I am new to story boards and I am trying to move between table view controllers.
I understand how to set up a segue and how to send data to the new story board but my question is returning data back. If I use the push style segue it gives me an automatic back button. I want a page that will create a "job" and save it if they hit save (a bar button I created on the other side of the title) When I set up the segue to go back to the main page from the save button it made that main table view controller a child (instead of simply going back to it's original state). The work around I was thinking was saving it to a file when they hit save and whenever they load the main table view it loads from that file. Is this a common and correct way to do this or should I be trying to return that object and save it in the main table view controller?
A common approach is to use delegation. Since ViewControllers should be as independent as it is possible, we have to minimize dependencies between them. And actually your idea with the file, if I understand it correctly, does it as well. But using files to organize a communication between ViewControllers is not very convinient.
Usually you declare a #protocol SecondViewControllerDelegate for your second ViewController (where you click a "Save" button). With methods like:
#protocol YourSecondViewControllerDelegate <NSObject>
-(void)yourSecondViewControllerDidCancel:(YourSecondViewController*)controller; //if you have a cancel button
-(void)yourSecondViewControllerDidFinish:(YourSecondViewController*)controller yourDataToReturn:(SomeData*)data andSomeMoreData:(AnotherDataType*)data2;
#end
Then you add a property like this to your SecondViewController:
#property (nonatomic, assign) id<YourSecondViewControllerDelegate> delegate; //Do not retain it!
Then you adopt your protocol in your MainViewController.
#interface MainViewController()<YourSecondViewControllerDelegate> //you can do it in the private category to keep the class interface clear.
#end
#implementation
//don't forget to implement those methods here :)
#end
And when you trigger your segue from the MainViewController, you can set your MainViewController as a delegate for the SecondViewController:
SecondViewController *destinationController = [[SecondViewController alloc] init]; //Just for example.
destinationController.delegate = self;
//trigger a segue :)
When the user presses the Save button in the SecondViewController, you call the delegate's yourSecondViewControllerDidFinish:
[self.delegate yourSecondViewControllerDidFinish: self yourDataToReturn: someData andSomeMoreDate: someMoreData];
This way MainController's yourSecondViewControllerDidFinish method will be called. And you can pick someData and someMoreData up there.
You can get more details about it in this tutorial:
Apple's your second iOS app tutorial
If you're making a segue to go "back" to a controller, then that segue needs to be an unwind segue. An unwind segue goes back to the same instance of the controller that you came from originally. If you need to send data back to that controller, you can do it in prepareForSegue.
Related
Problem
As illustrated, I have a container view (B) which resides inside of a view with other controls on it (A). The container view (B) holds a collection view, which I would like to update whenever a button is pressed on view (A).
I have gone through the UICollectionView Basics but feel I must be missing something. My natural response when wanting to communicate between UIViewControllers is to go off and build something based on either a callback or other delegate mechanism. Before I reinvent the wheel, any thoughts?
Currently when I click the buttons in the view, my collection data is updated and I call setNeedsDisplay and reloadData on the collection view (accessed via the childViewControllers property). I've tried calling setNeedsDisplay on the container view itself as well (no joy).
BTW - I have reviewed similar SO questions, which do not provide a matching use-case but do seem to indicate a lack of insight on this particular type of issue (if I've missed a great answer please let me know):
A, B, C, D
Solution
Please note that I've shared my solution below but additional answers are still welcome (especially if it's a better way)
Solution
Since my search of the community showed marginal information on this topic, I am sharing my solution. I added a callback function on my data controller. Then upon closer inspection of How to tell UICollectionView about your content, I realized that I could simplly toggle the data source as indicated below (so I did a combination of both):
Please note the multiple options mentioned on how to get a UICollectionView to update itself.
Details
In my .h file for my data controller
//above #interface
typedef void (^CallbackBlock)();
//within #interface
#property (strong, nonatomic) CallbackBlock onDataUpdated;
//within my data controller method where data was changed
[DataManager sharedManager].onDataUpdated(); //singleton, i know...
//alternative to above singleton
__weak DataManager* weakSelf = [[DataManager alloc]init];
weakSelf.onDataUpdated();
//In my UIViewController (B) subclass
#property (strong,nonatomic)DataManager* controller;
//In my view did load of UIVC (B)
self.controller = [DataManager sharedManager];
__weak ProgressViewController* weakSelf = self;
self.controller.onDataUpdated = ^(){
//perform my action
[weakSelf setData:nil]; //remove existing data
[weakSelf setData:[self getSomeData]]; //get some new data
[[weakSelf progressCollectionView]reloadData];
[[weakSelf progressCollectionView] setNeedsDisplay];
};
End result:
UIVC (A) receives button click
Data Manager class fires the data updated event at end of method execution for step 1
UIVC (B) instantly reloads the data without refreshing other subviews of UIVC (A)
I have multiple buttons in ViewA. When a button is clicked, it is redirected to ViewB. In ViewB, the user does some interactions, and then clicks the back button when he is done. How do I run a method and pass in a parameter from ViewB into ViewA and then continue working in ViewA?
Using the back button is strongly required, but I am keen to learn if there are other ways.
My idea was to get the ViewA from the stack, and when I am done with ViewB, just call upon it and redirect, but I couldn't figure out how to do it.
Thank you.
You want to define a delegate in ViewB and implement it in ViewA. At the appropriate time (e.g., when the back button is tapped) ViewB call the delegate method, passing the value as a parameter.
Something like this:
ViewB.h
// Define a delegate protocol allowing
#protocol ViewBDelegate
// Method of delegate called when user taps the 'back' button
-(void) backButtonSelected:(id) object;
#end
#interface ViewB : UIViewController
// property to hold a reference to the delegate
#property (weak)id<ViewBDelegate> delegate;
-(IBAction)backButtonSelected:(id)sender;
#end
ViewB.m:
#implementation ViewB
...
-(IBAction)backButtonSelected:(id)sender{
NSObject *someObjectOrValue = nil;
// Invoke the delegates backButtonSelected method,
// passing the value/object of interest
[self.delegate backButtonSelected:someObjectOrValue];
}
#end
ViewA.h:
#import "ViewB.h"
// The identifier in pointy brackets (e.g., <>) defines the protocol(s)
// ViewA implements
#interface ViewA : UIViewController <ViewBDelegate>
...
-(IBAction)someButtonSelected:(id)sender;
#end
ViewA.m:
-(IBAction) someButtonSelected:id
#implementation ViewA
...
// Called when user taps some button on ViewA (assumes it is "hooked up"
// in the storyboard or IB
-(IBAction)someButtonSelected:(id)sender{
// Create/get reference to ViewB. May be an alloc/init, getting from storyboard, etc.
ViewB *viewB = // ViewB initialization code
// Set `ViewB`'s delegate property to this instance of `ViewA`
viewB.delegate = self;
...
}
// The delegate method (defined in ViewB.h) this class implements
-(void)backButtonSelected:(id)object{
// Use the object pass as appropriate
}
#end
In iOS6 you can now use Unwind Segues. In Storyboard you might have noticed the new Exit buttons that can be used for this.
Unwind Segues will allow you to transition back from viewControllerB to viewControllerA and to provide info back through the prepareForSegue method.
Currently documentation is rather limited, but there is a useful WWDC2012 video on how to do this (Session 236)
If you don't want to implement protocol then other way is, you can create property in ViewB of type id like this
#property (nonatomic, unsafe_unretained) id* parentView;
And set it to ViewA when pushing ViewB like this
ViewB.parentView = ViewA;
//push ViewB
Then you can call ViewA's methods directly using this property.
You can define a protocol in ViewControllerA, example name ViewControllerADelegate, create a property delegate in ViewControllerB and in ViewControllerB implement the protocol defined in ViewControllerA.
When you are working in viewControllerB you can notify all your changes to viewControllerA calling methods defined in ViewControllerADelegate, when you will leave viewControllerB you will have viewControllerA updated.
If your ViewA is creating ViewB, you can create a protocol on your ViewB.h and set ViewA as ViewB's delegate. So whenever you change something in ViewB, you can notify ViewA directly.
Depending on your model, you can have a class that holds whichever settings you modify in ViewB. When you create ViewB, you pass the reference normally, and since it's an object, the instance will be shared, so when you go back to ViewA, all the changes are already applied.
viewB.settings = self.settings; // or whatever model object you have
Another option is to create a custom back button (you will lose the pointy look), have it call your custom method that notifies your ViewA and then does a [self.navigationController popViewControllerAnimated:YES];
I have a UISplitViewController with the master view set up like this:
UITabBarController
Tab1:
UINavigationController -> UIViewController -> UIViewController
Tab2:
UINavigationController -> UIViewController
Each of the UIViewControllers is a table view, and when the user chooses a row in the last one, an image is shown in the detail view, which contains a UIScrollView.
The tab bar Controller is the UISplitViewControllerDelegate and handles putting up the button on a toolbar at the top of the scroll view.
The problem is, I want to add code to dismiss the popover when the user makes their choice. The pointer to the popover has to be saved in the tab bar controller when the button goes up, and then used to dismiss the popover several view controllers down the line when the user makes their final selection. There doesn't seem to be any way for the view controller that needs that pointer to get at it, without doing something gross like storing it in the App Delegate.
I don't see other people asking this question, which leads me to believe that I've once again overlooked something simple. Please enlighten me!
It sounds like your tab bar controller is already a subclass of UITabBarController, which means that you've already got some custom code in there. I would suggest that the tab bar controller is the primary owner of the popover, and it is the table view controller's responsibility to simply notify the tab bar controller that a selection has been made. The tab bar controller can respond to that message by dismissing the popover. You can take advantage of the fact that UIViewController already has a method for accessing the tab bar controller that contains a given controller.
So it would look something like this:
#interface MyTabBarController : UITabBarController
- (void)itemWasSelected;
#end
#implementation MyTabBarController {
UIPopoverController *popover;
}
- (void)itemWasSelected {
[popover dismissPopoverControllerAnimated:YES];
}
#end
//////////////
#implementation TableController
- (void)tableView:(UITableView *)tv didSelectRowAtIndexPath:(NSIndexPath *)path {
// Do whatever else you want to do
MyTabBarController *tabController = (MyTabBarController *)self.tabBarController;
[tabController itemWasSelected];
}
With this solution, the table controller doesn't have to know anything about the popover; it just has to know that it's going to be presented inside a MyTabBarController, which seems a reasonable thing for it to know.
You could create a singleton class to track your popover status and then make it available to all classes equally and easily. That way it could easily be updated and accessed from any code without having to go straight to overburdening the app delegate even though thats basically the same idea but a bit cleaner in its own singleton.
I have a view based app (not navigation or tab based...)
My main view controller is called from the app delegate and initiated from a xib.
Then I use presentModalViewController to bring another view on the screen with it's own xib and view controller.
I have no problems passing data to that view controller.
However, when I dismiss the second view controller, I want to send data back to the main view controller for my app, but I just can't figure out how to reference it. Actually, I'd like to call a method in the main view controller if possible.
I've been struggling with this a bit and have found suggestions online but I just can't seem to get it to work. I'm hoping someone can provide the sample code to do this.
P.s. is this "main view controller" still referred to as a "root view controller" or is that term only used when dealing with a view controller stack (i.e. navigation or tab view controller)
EDIT:
I'm sure Bryan's solution would work so I have accepted as answer. However I ended up using NSNotificationCenter to get this to work and I find it a bit simpler to understand as a beginner
You can use the delegation pattern. In your modal view controller's header file, create an interface for a new delegate protocol...
#protocol ModalViewControllerDelegate <NSObject>
- (void)sendData:(Data *)someData;
#end
...and give your ModalViewController a new instance variable that implements this protocol:
#property (nonatomic, assign) id<ModalViewControllerDelegate> delegate;
Your main view controller should implement this protocol...
#interface MainViewController : UIViewController <ModalViewControllerDelegate> {
...and set itself as the delegate before it presents the modal view controller:
ModalViewController *modalViewController = [[[ModalViewController alloc] init] autorelease];
[modalViewController setDelegate:self];
// Present modal view controller
The main view controller should implement the delegate protocol's method:
- (void)sendData:(Data *)someData {
NSLog("I have just received some data: %#", someData);
}
Then inside your modal view controller, you can simply call the following method whenever you want to send data back to the main view controller:
[delegate sendData:someData];
In Objective C (Cocoa) I have an app running with a modal sheet, but I want to allow the app to quit even when the sheet is displayed (contradicting the definition of modal I think, but I like the animated effect of modal sheets).
I'm already using the -setPreventsApplicationTerminationWhenModal method and it works fine, but I'm wondering... is there any way to keep the close button enabled? The little circle usually red-colored close button that comes with all windows in the top left corner (side by side with minimize and maximize)? Right now it's completely disabled when the sheet is running, and it would be awesome if there is a way to enable it.
Thanks!
Use a delegate method to close the Modal View. You declare the delegate on your modal view controller and that delegate method dismisses the ModalViewController
In the Modal ViewController Interface File:
#protocol MyViewControllerDelegate
-(void)dismissModal;
#end
Then declare the delegate as a class property in the Modal ViewController:
#property (nonatomic, retain) id <MyViewControllerDelegate> delegate;
Now, declare your parent ViewController as a proper delegate implementer for the Modal ViewController:
#interface MyParentViewController : UIViewController
Then in the calling (parent) ViewController implement the delegate method in the implementation file:
-(void)dismissModal
{
// Dismiss the Modal ViewController that we instantiated earlier
[self dismissModalViewControllerAnimated:YES];
}
That should do it. The advised way to handle this is through delegate methods (and delegate methods are so handy to use whenever a process in one controller needs to fire a method in another controller. It is well worth the time to get familiar with using delegates to get work done in Obj C