I have a view controller with an image view in it.
I have a popover with a table view in it which is anchored to a bar button in this view controller.
I would like to be able to load images into the image view by using the table in the popover.
Both the popover and the main view controller have separate view controller classes.
I have launched the popover from a segue.
How can I do this?
I am assuming that your segue takes you from your imageViewController to your popped-over tableViewController.
Then you can set your imageViewController as delegate to the tableViewController, so that you can call methods on it from the tableViewController in a decoupled manner.
MyTableViewController.h
In your tableViewController header file declare a protocol which it will expect it's delegate to follow. Place it above your #interface section:
#protocol MyTableViewControllerDelegate <NSObject>
- (void) dismissPopoverAndLoadImage:(NSString*)imageName;
#end
Also declare a property to hold a reference to it's delegate:
#property (nonatomic, weak) id <MyTableViewControllerDelegate> delegate;
The protocol declares the method signature that your tableView will expect to be able to call on its delegate. It allows it to send back some data, and get itself dismissed. The delegate (in this case, your imageViewController) will have to implement this method.
MyTableViewController.m
The method is called on the delegate when a table cell is selected:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell* cell = [tableView cellForRowAtIndexPath:indexPath];
NSString* imageName = cell.textLabel.text;
[self.delegate dismissPopoverAndLoadImage:imageName];
}
MyImageViewController.h
include MyTableViewController.h and add the delegate protocol to the #interface.
#include "TableViewController.h
#interface MyImageViewController: UIViewController <MyTableViewControllerDelegate>
Declare a property to hold a reference to your UIPopOverController so that you can send it a dismiss message:
#property (nonatomic, weak) UIPopoverController* seguePopoverController;
(these steps could be moved to your .m file's category extension for better encapsulation).
MyImageViewController.m
You will set the delegate property in MyImageViewController's prepareForSegue method, which gets called when the segue is invoked.You will also set the reference to the popoverController here.
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"popoverTable"]) {
self.seguePopoverController = [(UIStoryboardPopoverSegue*)segue popoverController];
[segue.destinationViewController setDelegate:self];
}
}
}
Lastly, you implement the tableViewController's delegate method:
- (void) dismissPopoverAndLoadImage:(NSString*)imageName
{
self.imageView.image = [UIImage imageNamed:imageName];
[self.seguePopoverController dismissPopoverAnimated:YES];
}
update
Aside from the fact that the popOverController itself is a slightly unusual entity (a controller without a view, inheriting directly from NSObject), most of this is the standard delegation pattern. You could simplify it somewhat by using a bit of indirection and runtime checking in didSelectRowAtIndexPath:
if ([[self delegate] respondsToSelector:#selector(dismissPopoverAndLoadImage:)])
[[self delegate] performSelector:#selector(dismissPopoverAndLoadImage:)
withObject:imageName];
In this case you would not need to define the protocol or <adhere> to it, and you wouldn't need to #import MyTableViewController. However the compiler would give you no help if you did not implement the method correctly. Which, as you can see from my earlier mistake, is probably unwise.
Related
I have 3 controllers:
SearchViewController
CollectionViewController
MainViewController
In short
In CollectionViewController I can access SearchViewController by tapping the magnify glass in the nav bar. I can then search for a specific item. Then select a result from the rows returned. Once I tap a row the SearchViewController is dismissed and a query is performed on the CollectionViewController.
However on MainViewController things don't work the same. Because when the SearchViewController is accessed from the MainViewController and dismissed it's the MainViewController that appears because that is where the SearchViewController was initialized from so I have to do some extra work to get me to the CollectionViewController to perform the query. I performed in first paragraph.
There is a protocol/delegate method I set in the MainViewController that notifies the CollectionViewController when the SearchViewController is dismissed that is causing the app to crash. I can't figure out why. Please read on to for a better explanation of what is going on.
In long
Customer types what they want to search for in the search box of the SearchViewController and when they tap the row in the associated tableView of results the SearchViewController is dismissed and it's parent CollectionViewController is now on screen. When this controller is dismissed I use a delegate to notify CollectionViewController that the SearchViewController was dismissed.
Notify CollectionViewController about dismissal of SVC then perform a query:
- (void)searchViewControllerDismissed:(VAGSearchViewController*)searchViewController withTitleForObject:(NSString *)titleString
{
_searchTitleString = titleString;
[self setObjects:nil];
[self performQuery];
}
This all works fine. However I have another controller MainViewController. SearchViewController can be accessed from MainViewController but I can't dismiss and perform a query the way I did when I accessed SearchViewController from CollectionViewController because when the SearchViewController is dismissed MainViewController is present on screen because it was the controller I accessed the SearchViewController from.
What I decided to do was detect which controller initialised SearchViewController then use the same delegate method from above to notify that controller when the SearchViewController was dismissed.
Inside SearchViewController:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
// If count of child view controllers is greater than 1 then we are not on main table view controller
if ([[[self presentingViewController] childViewControllers] count] > 1) {
[self dismissViewControllerAnimated:NO completion:^{
[[self delegate] searchViewControllerDismissed:self withTitleForObject:[[cell textLabel] text]];
}];
} else {
[self dismissViewControllerAnimated:NO completion:^{
[[self delegate] searchViewControllerDismissed:self withTitleForObject:[[cell textLabel] text]];
}];
}
}
This works fine. So now I created a protocol inside MainViewController.
Inside MainViewController.h:
#class VAGMainTableViewViewController;
#protocol VAGMainTableViewControllerDisappearedDelegate <NSObject>
- (void)mainTableViewControllerDisappearedwithTitleForObject:(NSString *)titleString;
#end
#interface VAGMainTableViewController : UITableViewController
#property (nonatomic, weak) id<VAGMainTableViewControllerDisappearedDelegate> disappearDelegate;
What I wanted was this delegate method to then notify CollectionViewController that SearchViewController had been dismissed from MainViewController then perform a query.
Here is the code inside MainViewController.m:
- (void)searchViewControllerDismissed:(VAGSearchViewController *)searchViewController withTitleForObject:(NSString *)titleString
{
self.disappearDelegate = self;
[[self disappearDelegate] mainTableViewControllerDisappearedwithTitleForObject:titleString];
[self performSegueWithIdentifier:#"garmentsCollectionSegue" sender:nil];
}
This is crashing with:
[VAGMainTableViewController mainTableViewControllerDisappearedwithTitleForObject:]: unrecognized selector sent to instance 0xf0140f0
When I comment out the line where I set the disappearDelegate line the crash goes away and I am pushed to the CollectionViewController (garmentsCollectionSegue). But the data passed in the delegate method is received by CollectionViewController obviously because a delegate is set.
I've done enough messing around and I think the problem I'm having is caused by not properly setting a delegate or not setting it in the correct place.
Would appreciate some help.
Thanks for your time.
It seems that you are setting the MainViewController as delegate of itself. I don't think that is what you want.
You want to use that delegate protocol to inform the CollectionViewController of what happens in the MainViewController, right?
Then CollectionViewController should be the disappearDelegate.
So try instead:
CollectionViewController *collectionVC = //this depends on your app structure, get the controller
self.disappearDelegate = collectionVC;
I am trying to pass data from a UITextView in one view controller to UITextView in another view controller. My application is using Storyboards.
in the first view controller I say:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
LyricKaraokeViewController *karaokeController = segue.destinationViewController;
karaokeController.hidesBottomBarWhenPushed = YES;
SongDoc *song = [[SongDoc alloc] initWithTitle:#"Title" lyrics:#"Test" thumbImage:nil];
karaokeController.detailItem = song;
NSLog(#"%#", karaokeController.detailItem.data.lyrics);
}
The NSLog outputs the appropriate text
In my second view controller I declare this interface:
#class SongDoc;
#interface LyricKaraokeViewController : UIViewController
#property (nonatomic, retain) SongDoc * detailItem;
#end
and this implementation ( just showing viewDidLoad for simplicity ):
- (void)viewDidLoad
{
NSLog(#"%#", self.detailItem.data.lyrics);
[super viewDidLoad];
}
and I confirm that there is the properties data.lyrics in the detailItem because we are passing in a SongDoc object and I output the SongDocs contents in prepareForSegue just prior....
My problem is in the second view controller I am receiving an error saying the detail item doesn't declare the property lyrics
But I know this is untrue so why is this happening?
any help would be great thanks :)
#class SongDoc;
only tells the compiler that SongDoc is a class, but does not read the implementation file. You have to
#import "SongDoc.h"
instead, so that the compiler knows which properties are declared by the class.
I trying to pass the data from TableViewCell to the another ViewController.But No data Displaying in the another ViewController.here is my Code
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
PeripheralManager *objSelected=[device objectAtIndex:indexPath.row];
[self prepareForSegue:#"TableDetails" sender:objSelectedDevice];
}
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"TableDetails"])
{
DetailViewController *detail=segue.destinationViewController;
detail.dataArray=device;
}
}
Error Message
nested push animation can result in corrupted navigation bar
2012-10-24 12:01:39.805 [3182:707] nested push animation can result in corrupted navigation bar
2012-10-24 12:01:40.164 [3182:707] Finishing up a navigation transition in an unexpected state. Navigation Bar subview tree might get corrupted.
2012-10-24 12:01:40.167 [3182:707] Finishing up a get navigation transition in an unexpected state. Navigation Bar subview tree might corrupted.
You do not need this:
[self.navigationController pushViewController:mdc animated:YES];
That is what Segue will do automatically
Also, you are having 3 lines that will load view controller - see below for comments:
NSInteger row=[indexPath row];
NSString *value=[device objectAtIndex:row];
MeBleDetailViewController *mdc=[self.storyboard instantiateViewControllerWithIdentifier:#"MeBleDetailViewController"];
mdc.deviceName=value;
[self presentModalViewController:mdc animated:YES]; // Load ViewController
[UIView commitAnimations];
[self performSegueWithIdentifier:#"TableDetails" sender:[device objectAtIndex:indexPath.row]]; // Load ViewController
[self.navigationController pushViewController:mdc animated:YES]; // Load ViewController
That is why you are getting that error: nested push animation can result in corrupted navigation bar
Also, If you have configured the segue from table cell to another view controller then you don't need anything in didSelectRowAtIndexPath method.
Edit:
Whatever data you want the pushed view controller to have - put it in prepareforSegue method instead of didSelectRowAtIndexPath
If you create a segue from table cell to view controller then you don't need to execute the following as this method is to execute the segue programmatically.
[self performSegueWithIdentifier:#"TableDetails" sender:[device objectAtIndex:indexPath.row]];
Remove your extra code Only do this-
In DetailViewController.h
#property(nonatomic, retain)NSMutableArray *dataArray;
In DetailViewController.m
#synthesize dataArray = _dataArray;
Now In TableViewController.m Just write this -
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if([segue.identifier isEqualToString:#"TableDetails"])
{
DetailViewController *detailViewObject = segue.destinationViewController;
detailViewObject.dataArray = anyArray;
}
}
Here I'm passing NSMutableArray.
OK. Let's say you have two viewcontrollers FirstViewController and SecondViewController.
In FirstViewController you have a tableview and of course tableviewcell. In SecondViewControlleryou need to display data.
So in SecondViewController.h you need to set a propery of some variable, in this case it is of id type #property (strong, nonatomic) id secDetailItem;. Synthesize it in SecondViewController.m and add a setter method like this
-(void)setDetdetailItem:(id)newSecdetailItem{
if (secDetailItem != newSecdetailItem) {
secDetailItem = newSecdetailItem;
// Update the view.
[self configureView];//This method is needed to update view if there are some changes in that view.
}
}
So then in FirstViewController.h import SecondViewController.h and add property #property (strong, nonatomic) SecondViewController *secondViewController; then
synthesize. In FirstViewController.m file in this delegate method do following:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
self.secondViewController.secDetailItem=//set your data should be passed.
//Also if you need to push viewcontroller add pushviewcontroller:SecondViewController, or use IB to connect tableview cell and SecondViewController together with push method.
}
In this case you will not need to use perform segue. The Setter method will work as soon as you set to the secDetailItem something.
Also if you need to update your view in SecondViewController add this method to it.
- (void)configureView
{
if (self.secDetailItem) {
self.textLabel.text=self.secDetailItem;//Data passed from FirstViewController
}
}
This is all you need to do. Sorry if it is complicated. Ask any question.
It might have something to do with this line:
[UIView commitAnimations];
You can delete it if you don't need it.
I have the following simple view controller class set up
#protocol ThermoFluidsSelectorViewControllerDelegate;
#interface ThermoFluidsSelectorViewController : UIViewController <UITextFieldDelegate>
#property (weak, nonatomic) id <ThermoFluidsSelectorViewControllerDelegate> delegate;
// user hits done button
- (IBAction)done:(id)sender;
#end
#protocol ThermoFluidsSelectorViewControllerDelegate <NSObject>
-(void) didFinishSelection:(ThermoFluidsSelectorViewController *)controller fluidID: (NSString *)fluidID;
#end
the 'didFinishSeletion: fluidID:' method is defined in the master view controller and should dismiss the selector view controller when called. When the done button is pressed the following method is called:
- (IBAction)done:(id)sender
{
[[self delegate] didFinishSelection:self fluidID:nil];
}
the 'done:' method gets called (checked with an alert) but 'didFinishSelection...' is not getting called so the view will not revert back to the main screen. Any ideas?
It sounds like you have not assigned your delegate in your master view controller.
You should have something like this in your master view controller which sets up the delegate:
ThermoFluidsSelectorViewController *view = [[ThermoFluidsSelectorViewController alloc] init];
view.delegate = self;
here you can see I create the view, then set the delegate of the view back to myself.
If you are not creating the Thermo... view controller programatically, but have used a storyboard, then you can set the delegate in the prepareForSegue: method of your master view controller:
// Do some customisation of our new view when a table item has been selected
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// Make sure we're referring to the correct segue
if ([[segue identifier] isEqualToString:#"MySegueID"]) {
// Get reference to the destination view controller
ThermoFluidsSelectorViewController *cont = [segue destinationViewController];
// set the delegate
cont.delegate = self;
Hope this helps.
I have a UITableViewController that has custom cells that I have customized with my own subclass.
In this subclass, I've added a button and I want to push a view into the stack of the navigation controller. I have no idea on how to do that since I don't know how I can access to the navigation controller from my custom cell.
Any idea?
Need more infos here. What class holds the table, what class is the tableview delegate?
In the most simple case you're working in one single class. Than it would be [self.navigationController pushViewController: xyz].
But if you have your own subclassed UITableViewCells, than you need to communicate between the cell class and the viewcontroller. You could do this via setting a property in the cell class or your own customCell delegate.
You could also just send a Notification ([[NSNotificationCenter defaultCenter] postNotification: #"cellButtonTouchedNotification"]) on which your viewController is listening ([[NSNotificationCenter defaultCenter] addListener: self target: #selector(...) name: #"cellButtonTouchedNotification"]). In this case you could use the userInfo property to remember which cell was touched.
Anotherway is to make the button accessible from outside (a property eg). Then you could add the target in your tableViewDelegate's method cellForRowAtIndexPath:. Smth. like [myCustomCell.button addTarget: self selector: #selector(...)]; You could use tags to identify the row myCustomCell.button.tag = indexPath.row.
Use delegation. Here a simple example.
//.h
#protocol MyTableViewCellDelegate;
#interface MyTableViewCell : UITableViewCell
#property (assign, nonatomic) id <MyTableViewCellDelegate> delegate;
//your code here
#end
#protocol MyTableViewCellDelegate <NSObject>
#optional
- (void)delegateForCell:(MyTableViewCell *)cell;
#end
//.m
#implementation MyTableViewCell
#synthesize delegate = _delegate;
- (void)prepareForReuse {
[super prepareForReuse];
self.delegate = nil;
}
- (void)buttonAction {
if ([self.delegate respondsToSelector:#selector(delegateForCell:)])
[self.delegate delegateForCell:self];
}
#end
When you click the button you send a message to the delegate for your cell (e.g. the table view controller that is inserted into the navigation controller).
Here the controller
#implementation YourController
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *reuseIdentifier = #"MyCustomCell";
MyTableViewCell *cell = (id)[tableView dequeueReusableCellWithIdentifier:reuseIdentifier];
if (cell == nil)
cell = [[[MyTableViewCell alloc] initWithMyArgument:someArgument reuseIdentifier:reuseIdentifier] autorelease];
[cell setDelegate:self];
// update your cell
return cell;
}
- (void)delegateForCell:(MyTableViewCell *)cell {
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
// do your stuff
[self.navigationController pushViewController:...];
}
#end
Hold a pointer to your UITableViewController in the cell. You can pass it in the cell's constructor or set it later. Then you can call the pushViewController on the table view controller.
Even more beautiful solution would be to define a delegate for your cell, say ButtonCellDelegate that has a buttonClicked callback. You implement the delegate in your UITableViewController (or any other place where you have access to the view controller). Then you pass the delegate to the cell as described above and call the callback function from the cell when the button is clicked.