I have a UIViewController that calls a method on a singleton object in which, given a certain condition, displays a UIAlertView. When the user taps the button in the UIAlertView I want the UIAlertView to disappear (which at the moment it does) and then the UIViewController behind it to segue to another scene. My problem is that the UIAlertView is from the singleton class, while the segue needs to be performed on the UIViewController.
In Singleton.m:
-(void)alertView:(UIAlertView *)alertView
clickedButtonAtIndex:(NSInteger)buttonIndex {
NSString *buttonTitle = [alertView buttonTitleAtIndex:buttonIndex];
if ([buttonTitle isEqualToString:#"Button!"]) {
[self performSegueWithIdentifier: #"Segue!" sender: self];
}
}
My question is what do i replace self with that will allow me to notify the UIViewController that it's time to do a segue?
'Notify' is a good word. :) I suggest having the view controller listen for a NSNotification that the singleton will post when it wants the segue to happen. Then the perform... code would move into the view controller method that's called in response to the notification.
Related
I've got two UIViewControllers. I'm using modal segue to the second one, when coming back I use dismissViewControllerAnimated. I want to fire a method when I come back to the first one. How can I do that?
I tried to fire a custom notification before dismissViewControllerAnimated and catching it in the first UIViewController, but it doesn't catch anything, because it's still on the second one when it's fired.
There are easy options I can see.
Use the viewDidDisappear: method in the view you're dismissing.
dismissViewControllerAnimated:completion: method accepts a block that actually executes after viewDidDisappear executes in the dismissing view.
To pass a reference from one view controller to the next:
In the second view controller's .h file, add a property:
#property (nonatomic,strong) FirstViewController *firstVC;
In your first view controller, add the following method:
- (void)prepareForSegue:(UIStoryboardSegue*)segue sender:(id)sender {
if([#"segue_YOUR_SEGUE_NAME" isEqualToString:[segue identifier]]) {
if([[segue destinationViewController] isKindOfClass:
[SecondViewController class]]) {
SecondViewController *dest = (SecondViewController*)[segue
destinationViewController];
dest.firstVC = self;
}
}
}
Now, in your second view controller, you can do two things, as I already stated:
[self dismissViewControllerAnimated:YES
completion:^{
[self.firstVC someMethod];
}];
OR...
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
[self.firstVC someMethod];
}
To start I am building an app to learn the basics of Objective-C. If there is anything unclear please let me know and I will edit my question.
The app is supposed to have the next functionality.
Open the camera preview when the app is executed. On the top there is a button to go to a TemplateController where the user can select an array of frames to select from a UICollectionView. User selects the Template and returns to the Camera Preview. User takes a picture and the picture with the frame selected is shown in the PreviewController. If the user doesn't like the frame and wants to switch it for another one. PreviewController has button on top to go to the TemplateController, select the frame and go back again to the PreviewController with the new frame.
I do not want to create an object for the frame everytime. I want the AppDelegate to hold that object. To keep it alive per say?(sorry, English is not my mother tongue).
I was thinking to use NSUserDefaults BUT I really want to do it using the AppDelegate. So at this point NSUserDefaults is not an option.
Now, I am using storyboards with a navigation controller. A screenshot is available here
Right now when I pass from the TemplateController to my PreviewController my code looks like this:
Reaching TemplateController from MainController or PreviewController
- (IBAction)showFrameSelector:(id)sender
{
UIStoryboard *storyboard;
storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard_iPhone" bundle:nil];
TemplateController *templateController = [storyboard instantiateViewControllerWithIdentifier:#"TemplateController"];
templateController.frameDelegate = self;
[self presentViewController:templateController animated:YES completion:nil];
}
Passing the data from TemplateController to its controller's destiny (Either MainController or PreviewController)
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
_selectedLabelStr = [self.frameImages[indexPath.section] objectAtIndex:indexPath.row];
[self.collectionView deselectItemAtIndexPath:indexPath animated:NO];
[self dismissViewControllerAnimated:YES completion:^{
if ([self.frameDelegate respondsToSelector:#selector(templateControllerLoadFrame:)])
{
[self.frameDelegate performSelector:#selector(templateControllerLoadFrame:) withObject:self];
}
}];
}
This loads the selected frame in PreviewController
- (void)templateControllerLoadFrame:(TemplateController *)sender
{
UIImage *tmp = [UIImage imageNamed:sender.selectedLabelStr];
_frameImageView.image = tmp;
}
My problem is, I don't have very clear what changes I have to do on the AppDelegate(it is untouched right now). What would be the best approach to accomplish this?
Main issue is when Tamplate is chosen before taking the still image. If I select the frame after taking the picture then it displays.
I am not certain that I understand your question. Stuffing an object into the app delegate solution may not be the best way forward. In fact I believe you ought to look at the delegation pattern that is used by Apple to communicate between view controllers. Please note that you appear to be doing half of the delegate pattern already. For example you make your PreviewController a frameDelegate of the TemplateController.
So I would think you'd have something like the following to transfer information from TemplateController back to the PreviewController. Note that I've included prepare for segue as that is a common pattern to push a data object forward (it will be called if you connect a segue from the PreviewController to the TemplateController and in your action method call performSegueWithIdentifier:#"SegueTitle"). Use of the "templateControllerDidFinish" delegation method is a common pattern used to push information back from TemplateController when it closes.
TemplateController.h
#class TemplateController;
#protocol TemplateControllerDelegate <NSObject>
-(void) templateControllerDidFinish :(TemplateController*)controller;
#end
#interface TemplateController : UIViewController
#property (nonatomic, weak) id <TemplateControllerDelegate>delegate;
...
#end
TemplateController.m
//! The internals for this method can also be called from wherever in your code you need to dismiss the TemplateController by copying the internal
-(IBAction)doneButtonAction:(id)sender
{
__weak TemplateController*weakSelf = self;
[self dismissViewControllerAnimated:YES completion:^{
[self.delegate templateControllerDidFinish:weakSelf];
}];
}
PreviewController.h
#import "TemplateController.h"
#interface PreviewController<TemplateControllerDelegate>
...
#end
PreviewController.m
#implementation
...
-(void) templateControllerDidFinish :(TemplateController*)controller
{
self.dataProperty = controller.someImportantData;
...
}
...
-(void)prepareForSegue:(UIStoryboardSegue*)segue sender:(id)sender
{
if ( [[segue identifier]isEqualToString:#""] )
{
TemplateController *tc = [segue destinationViewController];
tc.delegate = self;
tc.data = [someDataObjectFromPreviewController];
}
}
To fix this situation a bit more:
Add a segue from the PreviewController to the TemplateController
(Ctrl-drag from Preview view controller to the Template Controller
in the document outline mode)
Name the segue identifier in the identity inspector
Change your code that presents the view controller from:
(IBAction)showFrameSelector:(id)sender
{
UIStoryboard *storyboard;
storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard_iPhone" bundle:nil];
TemplateController *templateController = [storyboard instantiateViewControllerWithIdentifier:#"TemplateController"];
templateController.frameDelegate = self;
[self presentViewController:templateController animated:YES completion:nil];
}
to
- (IBAction)showFrameSelector:(id)sender
{
[self performSegueWithIdentifier:#"SegueTitle"];
}
Add your data object to the target view controller as noted in prepareForSegue and you will be in good shape. Then use the delegate method to catch any data returned from your template (just add the data as properties to the controller and you should be golden)
You can see a better example of this delegation in a utility project template from Xcode (I just keyed this in..) I hope this information helps. You can get more information at these resources and also by searching Google and SO for iOS delegation :
Concepts in Objective C (Delegates and Data Sources)
Cocoa Core Competencies
EDIT: Added code that contains the dismissal.
NEW DATA
The problem remains the same as the problem listed under old data, except the dismissal line has changed.
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
NSString *title = [alertView buttonTitleAtIndex:buttonIndex];
if([title isEqualToString:#"Yes"])
{
NSLog(#"Calling Dismissal...");
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
}
}
The function is being called because "Calling Dismissal..." is displayed in the log.
Current Hierarchy: UITabBarController - NavigationController/NavigationController - View1/View2
OLD DATA
In case the question was confusing, I am trying to dismiss a UITabBarController than I presented modally. The UITabBarController does use NavigationControllers to setup the two views inside. However, no matter how many parentViewController s I put in
(i.e.):
[self.parentViewController.parentViewController.etc... dismissViewControllerAnimated:YES completion:nil];
The UITableBarController will not dismiss. I have a button being placed in the NavigationController of both views that is calling the line of code above. Any hints on how to dismiss the UITableViewController?
The presenting view controller, which is not the same concept as the parent view controller, needs to dismiss it. This code:
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
will typically do the trick.
However, depending on what this controller's function is, I usually prefer to have my presented controllers send a delegate message or NSNotification to the parent, so the parent can extract whatever data it needs before dismissal.
I have a master-detail application that has a tableview in the masterViewController and in the detailview you can edit and add info into textfields and so on. I have a save button as the rightBarButtonItem in the detail view. If they did not save their work and they hit back I have a UIAlertView that popsup asking them if they want to save their work. Behind the alertview the detailview is still dismissed and the masterviewcontroller is now showing.
So when I hit save the tableview does not update. I have [tableview reloadData] in my masterviewcontroller viewWillAppear but it does not work because the viewappears behind the alertview before the user can hit save. I would like to call the [tableview reloadData] (or something similar) in this method here which is called from my detailview.
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
I can't just call [tableview reloadData] because the alertViews delegate is set as the detailview because it is pulling all the information from the textfields to save into the plist if they push save on the alertview. Is there a way I can call [tableView reloadData] from my alertView?
If I understand your problem correctly, you are dismissing the detailView before any button is tapped on the alertView. I don't know where you save your data and where the tableView retrieves it from but try waiting for user interaction on the alertView.
In the code where you pop the alertView to ask for save, do nothing else.
- (void)yourBackButtonAction //i assumed this is your method when they hit 'back'
{
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:title message:#"Would you like to save your work?" delegate:self cancelButtonTitle:#"No" otherButtonTitles:#"Yes", nil];
[alertView show];
[alertView release];
}
And in the delegate method, save the data and dismiss the detailView:
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (buttonIndex == 1) //yes
{
//save your data
[self.navigationController dismissModalViewControllerAnimated:YES];
}
else // no
[self.navigationController dismissModalViewControllerAnimated:YES];
}
Now in your masterViewController's viewWillAppear method, [self.tableView reloadData] should work.
I figured it out. What I did was created a property of UITableViewController in my DetailViewController.h file and named it masterView then synthesized it in its .m file. I then went into my masterViewController.m file and found the method tableView:didSelectRowAtIndexPath:and set my detailsViewController.masterView property to self (self being the masterViewController).
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
self.detailViewController.masterView = self;
}
I then went into my UIAlertView Save Method and called reloadData on the masterViews tableView
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (buttonIndex == 1) {
[masterView.tableView reloadData];
}
}
This is very much like passing info to an array or dictionary in the detail view from the tableView indexPath.row like this below (which is also in the method tableView:didSelectRowAtIndexPath:)
self.detailViewController.detailItem = object;
set the property of the tableview and try
[self.tableView reloadData]
I guess you have to create a delegate method for this.
Create a protocol,
Create one method inside for e.g. - (void) updateMyMasterTable;
Create a delegate instance like id <ProtocolName> obj; in your detail class & write in .h file.
Synthesize it.
When you want to reload your master's tableview call [obj updateMyMasterTable];
In your master class .m file implement - (void) updateMyMasterTable method & call [tableView reloadData];
I'm having a problem with my navigation controller. If I add a view controller to the stack:
- (void) tui_ToggleListStudy:(id)sender
{
listVC = [[ListViewController alloc] init];
[self.navigationController pushViewController:listVC animated:NO];
[listVC release];
}
I have NSLog messages for the view controller beneath, for both viewWillDisappear: and viewDidDisappear - but only viewWillDisappear: is getting called.
Not only that, but the view controller doesn't receive any other delegate messages either: No viewDidUnload, or dealloc...
Is there anything I can do about this?
I'm stumped! Any thoughts?
Thanks!
I know the answer if you made the same typo in your code that you made in your question: the method signature is viewDidDisappear: (with the animated argument), not viewDidDisappear.
Not only that, but the view controller doesn't receive any other delegate messages either: No viewDidUnload, or dealloc...
A view controller will not be deallocated when you push another controller onto the stack. And viewDidUnload won't be called unless you run out of memory.
Assuming your navigation controller is contained in some kind of top view controller, you must also forward the relevant messages from that top view controller to the nav controller:
-(void)viewWillAppear:(BOOL)animated
{
[navController viewWillAppear:animated];
}
-(void)viewDidAppear:(BOOL)animated
{
[navController viewDidAppear:animated];
}
-(void)viewWillDisappear:(BOOL)animated
{
[navController viewWillDisappear:animated];
}
-(void)viewDidDisappear:(BOOL)animated
{
[navController viewDidDisappear:animated];
}
You must call super at implementation of viewWillDisappear.
The designated initializer for UIViewController is -initWithNibName:bundle:. Are you sure your view controller is finding its nib and finds its connected view? I'll bet if you set a breakpoint after init'ing your ListViewController, you'll find its -view returns nil.