UIViewController is deallocated before delegate method is called - ios

I have PreviewController : UIViewController which is used to show document in full screen it's part of UINavigationViewController.
Also it's delegate of UISplitViewController.
When it's pushed, it hides masterView of UISplitViewController. (Methods 1 - 3 - 2 are called)
When it disappears (by pressing "back" button), it shows masterView of UISplitViewController. (Methods 4 - 2 are called)
But there is a way to force closing PreviewController. In this case only method 4 is called and PreviewController is deallocated before UISplitViewController can send message and call Method 2.
How can I resolve this problem? Is there a way to force UISplitViewController to call it's delegate method? Or can I retain PreviewController in Method 4 and release it in Method 2 (using ARC)?
// Method 1
- (void)viewDidLoad
{
[super viewDidLoad];
self.hideMaster = YES;
UISplitViewController *splitViewController = [(AppDelegate *)[[UIApplication sharedApplication] delegate] splitViewController];
splitViewController.delegate = self;
}
// Method 2
- (BOOL)splitViewController:(UISplitViewController *)svc shouldHideViewController:(UIViewController *)vc inOrientation:(UIInterfaceOrientation)orientation
{
return self.hideMaster;
}
// Method 3
- (void)viewWillAppear:(BOOL)animated
{
self.hideMaster = YES;
UISplitViewController *splitViewController = [(AppDelegate *)[[UIApplication sharedApplication] delegate] splitViewController];
[splitViewController.view setNeedsLayout];
[splitViewController willRotateToInterfaceOrientation:self.interfaceOrientation duration:0];
}
// Method 4
- (void)viewWillDisappear:(BOOL)animated
{
self.hideMaster = NO;
UISplitViewController *splitViewController = [(AppDelegate *)[[UIApplication sharedApplication] delegate] splitViewController];
[splitViewController.view setNeedsLayout];
[splitViewController willRotateToInterfaceOrientation:self.interfaceOrientation duration:0];
}
// PreviewController is created in UIViewController which belongs to UINavigationController
PreviewController *previewVC = [[PreviewController alloc] initWithNibName:#"PreviewController" bundle:nil];
previewVC.documentURL = url;
[self.navigationController pushViewController:previewVC animated:YES]

You're currently not holding any strong reference to your PreviewController instance.
It should be enough to add a strong class property in your interface:
#property (nonatomic, strong) PreviewController *previewVC;
And then create the instance by using:
self.previewVC = [[PreviewController alloc] initWithNibName:#"PreviewController" bundle:nil];
This ensures the PreviewController will not be deallocated while your view controller is alive (unless you release it yourself).

Related

Objective c - Detect current view from appDelegate

I'm using modal segue (without navigation controller) to move between viewController A and viewController B like so:
viewA *first = [self.storyboard instantiateViewControllerWithIdentifier:#"viewA"];
[self presentViewController:first animated:YES completion:nil];
And to move back :
[self dismissViewControllerAnimated:YES completion:nil];
Now, I want to know from the AppDelegate whether A or B is the current view right now.
The problem is when I'm checking
[(AppDelegate *)[[UIApplication sharedApplication] delegate] window]
the answer is always view A - the first one.
I've tried to set the current view every time I'm using modal segue like so:
viewA *first = [self.storyboard instantiateViewControllerWithIdentifier:#"viewA"];
[self presentViewController:first animated:YES completion:^
{
[[(AppDelegate *)[[UIApplication sharedApplication] delegate] window] setRootViewController:first];
}];
But it cause a few bugs (like unable to use "dismissViewControllerAnimated"),and it's impossible to work like that in every segue in a big project with many segues.
How should I work with that? And how should I detect the current view in more appropriate way?
As was answered here
UIWindow *topWindow = [[[UIApplication sharedApplication].windows sortedArrayUsingComparator:^NSComparisonResult(UIWindow *win1, UIWindow *win2) {
return win1.windowLevel - win2.windowLevel;
}] lastObject];
UIView *topView = [[topWindow subviews] lastObject];
However, doing this logic here sounds like bad architecture. What is the reason for you needing to know which view is currently presented inside of your AppDelegate?
Edit It seems like you want to respond to the applicationWillResignActive event from your view controller. Use something like this in the your game view controller.
- (void) applicationWillResign {
NSLog(#"About to lose focus");
}
-(void) viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(applicationWillResign)
name:UIApplicationWillResignActiveNotification
object:NULL];
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
[[NSNotificationCenter defaultCenter]
removeObserver:self];
}
The appDelegate's window won't be equal to either view controller (viewControllerA or viewControllerB). You can ask the window for it's root view controller...
AppDelegate *appDelegate = [UIApplication sharedApplication].delegate;
if (appDelegate.window.rootViewController == viewControllerA) {
// always true if you always start the app with viewControllerA
... and you can ask any view controller for the view controller it presented...
if (appDelegate.window.rootViewController.presentedViewController == viewControllerB) {
// will be true if viewControllerA has presented viewControllerB
But this is a tricky game. If, for example, viewControllerB presents some other viewControllerC, the condition above will continue to be true.
See the #Eric answer here (not the accepted answer) for a way to find the topmost vc in general.

UIViewControllers being created but not shown in state restoration

I have an app that is not working properly with state restoration. It previously did, but as I started moving away from the storyboard it stopped.
My app starts with a LoginViewController that is the starting view controller in my storyboard. If the login is successful, then it tries to add two FolderViewController to a navigation controller. This is so that the visible folder is one level deep already. This is done in the following code:
UINavigationController *foldersController = [[UINavigationController alloc] initWithNavigationBarClass:nil toolbarClass:nil];
foldersController.restorationIdentifier = #"FolderNavigationController";
FolderViewController *root = [storyboard instantiateViewControllerWithIdentifier:#"FolderView"];
root.folderId = 0;
FolderViewController *fvc = [storyboard instantiateViewControllerWithIdentifier:#"FolderView"];
fvc.folderId = 1;
[foldersController setViewControllers:#[root, fvc] animated:YES];
[self presentViewController:foldersController animated:YES completion:nil];
The FolderViewController has this awakeFromNib
- (void)awakeFromNib
{
[super awakeFromNib];
self.restorationClass = [self class]; // If we don't have this, then viewControllerWithRestorationIdentifierPath won't be called.
}
Within the storyboard the FolderViewController has a restorationIdentifier set. When I press the Home button, the app is suspended. My restoration calls in FolderViewController are being called:
// This is being called
- (void)encodeRestorableStateWithCoder:(NSCoder *)coder
{
[super encodeRestorableStateWithCoder:coder];
[coder encodeInt64:self.folderId forKey:#"folderId"];
}
The problem now is when I try and restore. I stop the app in my debugger and then start it up again. This kicks off the restoration process.
First, my viewControllerWithRestorationIdentifierPath:coder: for my LoginViewController is called. This doesn't do much, and its use is optional. I've tried removing it and I don't have any ill effect.
+ (UIViewController*) viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder
{
LoginViewController* vc;
UIStoryboard* sb = [coder decodeObjectForKey:UIStateRestorationViewControllerStoryboardKey];
if (sb)
{
vc = (LoginViewController *)[sb instantiateViewControllerWithIdentifier:#"LoginViewController"];
vc.restorationIdentifier = [identifierComponents lastObject];
vc.restorationClass = [LoginViewController class];
}
return vc;
}
Next, the viewControllerWithRestorationIdentifierPath:coder: for my FolderViewController is called:
// This is being called
+ (UIViewController*) viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder
{
FolderViewController* vc;
UIStoryboard* sb = [coder decodeObjectForKey:UIStateRestorationViewControllerStoryboardKey];
if (sb)
{
vc = (FolderViewController *)[sb instantiateViewControllerWithIdentifier:#"FolderView"];
vc.restorationIdentifier = [identifierComponents lastObject];
vc.restorationClass = [FolderViewController class];
vc.folderId = [coder decodeInt32ForKey:#"folderId"];
}
return vc;
}
I've previously had a decodeRestorableStateWithCoder: as well, and it did get called. However, since it's setup in the viewControllerWithRestorationIdentifierPath:coder:, it wasn't necessary to keep it around.
All of these things are being called the appropriate number of times. But in the end, the only view controller that is displayed in the LoginViewController. Why are my FolderViewControllers not being displayed. Is there a missing setup that I need to do in my LoginViewController to attach the view controllers that I manually added previously?
Edit
After reading http://aplus.rs/2013/state-restoration-for-modal-view-controllers/ which seemed relevant, I added the following code to the App delegate:
- (UIViewController *)application:(UIApplication *)application viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder
{
if ([identifierComponents.lastObject isEqualToString:#"FolderNavigationController"])
{
UINavigationController *nc = [[UINavigationController alloc] init];
nc.restorationIdentifier = #"FolderNavigationController";
return nc;
}
else
return nil;
}
I think the App is happier now, but it still isn't restoring properly. Now I get this error in my log:
Warning: Attempt to present <UINavigationController: 0xbaacf50> on <LoginViewController: 0xbaa1260> whose view is not in the window hierarchy!
It's something different.
I had similar issue. My stack of view controllers was pretty simple: root view with table view -> some details view -> some edit details view. No navigation views, views are modal. And it did not work.
Turned out, the issue was that, viewControllerWithRestorationIdentifierPath() in the root view controller should look like this:
+ (UIViewController *) viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents
coder:(NSCoder *)coder
{
NSDictionary *myRestorableObj = [coder decodeObjectForKey:#"myRestorableObj"];
// #todo Add more sanity checks for internal structures of # myRestorableObj here
if (myRestorableObj == nil)
return nil;
return [[UIApplication sharedApplication] delegate].window.rootViewController;
}
Instantiating new root view controller is wrong. It creates new stack of view controllers that the stored children view controllers down the stack do not belong to.
All the other children view controller should be created as usually, with the following code:
UIViewController *vc = [storyboard instantiateViewControllerWithIdentifier:#"MyChildViewController"];
Hope this helps.
You need to assign different restoration identifiers to different FolderViewController objects.
For example:
FolderViewController *folderViewController1 = // initialize object ;
FolderViewController *folderViewController2 = // initialize object ;
folderViewController1. restorationIdentifier = #"folderViewController1";
folderViewController2. restorationIdentifier = #"folderViewController2";
I tried the code above and it worked fine.

Calling a Function in MasterView after dismissing the ModalView in ipad

I am using Master-Detail template for ipad. I have a ViewController, which I want to show modally so I have used this code
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
m_ViewController = [[ViewController alloc] initWithNibName:#"ViewController" bundle:nil];
m_ViewController.modalPresentationStyle = UIModalPresentationFormSheet;
[appDelegate.splitViewController presentModalViewController:m_ViewController animated:YES];
This works fine and the ViewController is loaded modally, Now I tried to dismiss this ViewController, So inside ViewController.m, I called this line of code
[self dismissModalViewControllerAnimated:YES];
This code also works fine and the ViewController gets dismissed, But after dismissing I want to call a function in my MasterView. How to do that?
Code added according to the discussion with Moxy.
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
[appDelegate.testViewController testfunction:testImage];
As amit3117 pointed out, you should use a delegate.
The protocol should be defined at least with a method that would communicate to the delegate that the view controller that was presented modally did finish its work.
#class ViewController;
#protocol MyViewControllerDelegate <NSObject>
-(void)viewControllerDidFinish:(ViewController *)sender;
#end
EDIT : I forgot to add that you should a public property for the delegate to ViewController
#interface ViewController : UIViewController
#property (nonatomic, weak) id <MyViewControllerDelegate> delegate;
#end
You could use your master view controller as the delegate. So in your master view controller implementation you would also have :
#interface MyMasterViewController () <MyViewControllerDelegate>
#end
#implementation MyMasterViewController
-(void)showViewController
{
m_ViewController = [[ViewController alloc] initWithNibName:#"ViewController"
bundle:nil];
m_ViewController.modalPresentationStyle = UIModalPresentationFormSheet;
m_ViewController.delegate = self;
// –presentModalViewController:animated: is deprecated!
[self.parentViewController presentViewController:m_ViewController
animated:YES
completion:nil];
}
-(void)viewControllerDidFinish:(ViewController *)sender
{
// Add any code you want to execute before dismissing the modal view controller
// –dismissModalViewController:animated: is deprecated!
[self.parentViewController dismissViewControllerAnimated:YES
completion:^{
// code you want to execute after dismissing the modal view controller
}];
}
#end
When m_ViewController finishes its work, it should call :
[self.delegate viewControllerDidFinish:self];

iOS - login view before uitabbarcontroller

I want to display interface UITabBar when login succeeds.
I declare interface UITabBar in AppDelegate, but after login success I don't know how to call the interface.
Here is my code:
appdelegate.m
-(void)loadInterface
{
[self configureiPhoneTabBar];
}
-(void)configureiPhoneTabBar
{
UITabBarController *tabBarController = (UITabBarController *)self.window.rootViewController;
UIViewController *controller1 = [[tabBarController viewControllers] objectAtIndex:0];
[self configureTabBarItemWithImageName:#"home_ON.png" : #"home.png" andText:#"Trang chủ" forViewController:controller1];
UIViewController *controller2 = [[tabBarController viewControllers] objectAtIndex:1];
[self configureTabBarItemWithImageName:#"channel_ON.png" : #"tvChannel.png" andText:#"Kênh" forViewController:controller2];
}
and loginviewcontroller.m
- (IBAction)btnLogin:(id)sender {
[self performSegueWithIdentifier:#"idenLogin" sender:self];
AppDelegate *delegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
[delegate loadInterface];
}
Secondly, when you touch on button "play", layout video shows and it works ok, but I want to auto rotate
note: This is interface on iphone and I fix Portrait in Summary, I'm still show landscape, How to do?
Can u download my code demo is here
in couple of words you need modal view for login screen.
Here is how I did it (from app delegate class). Note that I have my login view designed in storytboard.
- (void) showLoginView
{
assert(loginController == nil);
assert(activityView == nil);
UITabBarController *tabbar = (UITabBarController *)self.window.rootViewController;
loginController = [tabbar.storyboard instantiateViewControllerWithIdentifier:#"LoginViewController"];
loginController.delegate = self;
[tabbar presentModalViewController:loginController animated:YES];
}
I create an object in my AppDelegate called WindowState or similar that manages what should be the rootViewController of the window. Initially it would be a sign in or splash, then you can run checks in your WindowState class and listen for notifications eg. MyAppDidSignInNotification then change the rootViewController of your app to a UITabBarController or whatever there.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.windowState = [[FASWindowState alloc] initWithWindow:self.window];
[self.window makeKeyAndVisible];
return YES;
}

ios How do I know the controller is a "push" or "pop" returned

i have three controller and i wanna kown the controller is a push or pop
A controller:
{
if(!b)
b = [B alloc] init];
[self.navigationController pushViewController:b animated:YES];
}
B controller:
- (void) viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
//I want here to judge, from the "A" push over, or to return from the "C" "pop"
//if it is push from A
//dosomething.....
//if it is pop from C
//dosomething
}
-(void)testAction:(id)sender
{
C *c = [[C alloc] init];
[self.navigationController pushViewController:b animated:YES];
[c release];
}
C controller:
{
[self.navigationController popViewControllerAnimated:YES];
}
thanks.
Have a look at the UIViewController method, isMovingToParentViewController. This will return YES if the view controller is being shown because it was pushed, but NO if it is shown because another view controller was popped off the stack.
-(void)viewDidAppear:(BOOL)animated { //Code in view controller B
[super viewDidAppear:animated];
NSLog(#"isMovingToParentViewController: %d",self.isMovingToParentViewController);
// this will log 1 if pushing from A but 0 if C is popped
}
EDIT :
Add UINavigationControllerDelegate in .h file
Also do this:
[self.yournavController setDelegate:self];
Method below is navigation controller delegate which is called when the navigation controller shows a new top view controller via a push, pop or setting of the view controller stack.
Add this method
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
}
Hmm I think for that you need to track a global variable that knows either it is push from A or pop from C. What I would do is:
Declare a BOOL variable isPush in appDelegate or some external .h file and synthesize it.
When you are going from A to B i.e. it is a push, make it equal "YES" in A.
yourAppDelegate *myDelegate = (yourAppDelegate*) [[UIApplication SharedApplication] delegate];
myDelegate.isPush = YES;
Similarly before popping from C, make value of isPush = NO;
In B's viewDidLoad, see the value of variable.
yourAppDelegate *myDelegate = (yourAppDelegate*) [[UIApplication SharedApplication] delegate];
if(myDelegate.isPush)
//means A was pushed
else
//means C was popped

Resources