I am trying to access an array defined as property on the parent view from child view and I get an empty array. here is what I did. could someone shed some light please!
FirstTableViewController.h
#interface FirstTableViewController : UITableViewController
#property (nonatomic,retain) NSMutableArray *SomeItems;
#end
in my FirstTableViewController.m. I have code that initialized the SomeItems with values. This has been verified
On the FirstTableViewController view there is a button that displays a second SecondTableView
in SecondTableViewController.m I have
#import "FirstTableViewController.h"
#import "SecondTableViewController.h"
- (void)viewDidLoad
{
[super viewDidLoad];
FirstTableViewController * objParent;
NSLog(#"count = %i",[objParent.SomeItems count]); //this return 0
}
thanks in advance!
EDITED
ToDoListTableViewController.h
#interface ToDoListTableViewController : UITableViewController
#property (nonatomic, strong) NSMutableArray *toDoItems;
- (IBAction)unwindToList:(UIStoryboardSegue *)segue : (id) sender;
#end
ToDoListTableViewController.m
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
//Attempt to pass toDoItems to new view controller.
//CompleteTableViewController.toDoItems = [self.toDoItems copy]; //this line caused compiler error saying toDoItems not found on object of CompleteTableViewController
CompleteTableViewController * objChild = (CompleteTableViewController*)[segue destinationViewController];
if(objChild != nil)
objChild.toDoItems = [self.toDoItems copy];
//sorry for weird code, as I don't really understand how this method really works.
//but I have a feeling I am just some inches away from getting it to work the way i want
}
CompleteTableViewController.h
#interface CompleteTableViewController : UITableViewController
#property (nonatomic,copy) NSMutableArray *toDoItems;
#end
CompleteTableViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(#"#%i record(s) in Complete Table View", [toDoItems count]);
}
Thanks again!
It is clear that you miss the basilar concept of the OOP.
You declared this:
FirstTableViewController * objParent;
but first of all it is not initializated and second it doesn't point to your instanced viewController.
Other things but that at this point become secondary are:
if you are using ARC, declare the property strong instead of retain;
method and variable should respect the Camel methodology so is not SomeItems but is someItems;
So if you are opening a viewController from another and you want access to that array, the best thing that you could do is pass that array and so have a property like this also in the second viewController:
#property (nonatomic,copy) NSArray *someItems;
and when you press the button in the first viewController, before push the second viewController:
secondViewController.someItems = [self.someItems copy];
Note that in the second view controller the array is not mutable, because probably you want just access to the informations. But this depend from you.
Is a little bit hard explain you more because i see that you are really a newbie. But, i hope to help you.
You need to pass a reference for the parent object through to the child on creation, or simply pass a reference to the array itself.
From Matteo suggestion, this seems to solve my issue
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
UINavigationController *navigationController = segue.destinationViewController;
CompleteTableViewController *objChild = (CompleteTableViewController *)navigationController.topViewController;
if(objChild != nil){
objChild.toDoItems = [self.toDoItems copy];
}
}
thanks Matteo.
In the SecondTableViewController, you've declared the FirstTableViewController in viewDidLoad, but that does not mean it's pointing to your previously created FirstTableViewController (you've made no assignment). Before viewDidLoad is called, you need to pass the FirstTableViewController to the SecondTableViewController, assign it to a variable that's not local in scope, and then you can access the FirstTableViewController and/or its SomeItems inside viewDidLoad.
Without knowing more of your design or what you're trying to accomplish, it'd be best to pass SomeItems to SecondTableViewController as part of initializing it if that's all you need from FirstTableViewController. If the state of what you're accessing is going to change as you are going from controller to controller, I'd highly recommend you spend more time reading up on Model-View-Controller (MVC), delegation and possibly Key-Value Observation (KVO). You're missing some fundamental knowledge.
Edit: also agree on naming, SomeItems should be someItems.
Related
I have four viewControllers in my current design and I am designing an app to sell a product.
FirstViewController gets the product image and when user clicks to the next button then it takes user to the secondviewcontroller where user describes the product and then user clicks next button which takes user to the thirdViewcontroller where price and condition are entered. In the lastviewcontolller there is a post button to send the product info to the server. I am using POST method.
The following segue approach does not fit into what I want, because it sends the firstviewcontroller object (product image) to the secondviewcontoller, and then secondviewcontroller also should forward the product image to the thirdviewcontoller and so on. I do not think it is a feasible way of doing it.
I wonder what is the best way of collection information from the first page till to the last page and send it. What is best way of handling that issue? I am using segue between the viewcontrollers.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// Make sure your segue name in storyboard is the same as this line
if ([[segue identifier] isEqualToString:#"isSecond"])
{
// Get reference to the destination view controller
SecondViewController *vc = [segue destinationViewController];
// Pass any objects to the view controller here, like...
[vc setMyProductImage:productImage];
}
}
Please don't use a singleton, even if the majority of users here tells you so. It would violate the SOLID-Principles for several reasons.
Instead just pass the object from ViewController to ViewController.
If all ViewController expect the same model class, you can create a common base class that has the property for the model.
it could have this method
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.destinationViewControler isKindOfClass:[ProductAwareBaseViewController class]])
{
ProductAwareBaseViewController *vc = (ProductAwareBaseViewController *)segue.destinationViewControler;
vc.product = self.product;
}
}
I created an example project: https://github.com/vikingosegundo/ProductWizard
Note, that all view controller derive from ProductAwareBaseViewController
#import UIKit;
#class Product;
#interface ProductAwareBaseViewController : UIViewController
#property (nonatomic, strong) Product *product;
#end
#import "ProductAwareBaseViewController.h"
#import "Product.h"
#interface ProductAwareBaseViewController ()
#end
#implementation ProductAwareBaseViewController
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.destinationViewController isKindOfClass:[ProductAwareBaseViewController class]]) {
ProductAwareBaseViewController *vc = (ProductAwareBaseViewController *)segue.destinationViewController;
vc.product = self.product;
}
}
#end
This ViewController knows how to pass the model data of class Product to other instances of ProductAwareBaseViewController and subclasses of it.
All other view controller don't deal with passing the data, just adding each portion of data (name, description, price) to the model and displaying it.
i.e:
#import "EditNameProductViewController.h"
#import "Product.h"
#interface EditNameProductViewController ()
#property (weak, nonatomic) IBOutlet UITextField *nameField;
#end
#implementation EditNameProductViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.product = [[Product alloc] init];
}
- (IBAction)continueTapped:(id)sender {
self.product.productName = self.nameField.text;
}
#end
#import "EditDescriptionProductViewController.h"
#import "Product.h"
#interface EditDescriptionProductViewController ()
#property (weak, nonatomic) IBOutlet UITextField *descriptionField;
#property (weak, nonatomic) IBOutlet UILabel *nameLabel;
#end
#implementation EditDescriptionProductViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.nameLabel.text = self.product.productName;
}
- (IBAction)continueTapped:(id)sender {
self.product.productDescription = self.descriptionField.text;
}
#end
Create an object to act as your application's data model. It can be a singleton or it can be a normal object that's available from a known location...such as owned by the app delegate.
Update your model when you have new information and read from the model when you need to display something. Using prepareForSegue: and linking controllers may be acceptable for simple things but it really doesn't scale well.
One way of doing this would be that you create a mutable dictionary (or a custom object with variables) in the first view controller. Then you would pass a weak reference to second/third/fourth view controllers of the mutable dictionary/object from first view controller. Each view controller would be able to set data to the dictionary/object and the last one would be able to process the information.
Another way would be to create a simple singleton class with variables that you want to store. The first view controller would reset the singleton variables. Then let each view controller access the singleton and store their values there, last view controller would process values from singleton.
It depends how many data you are collecting and what you personally prefer.
I have an NSMutableArray declared as property in .h and initialized in viewDidLoad in my SPOCVC .m (UIViewController)...
#property (strong, nonatomic) NSMutableArray* SPOCTrackList;
in viewDidLoad
if ([self SPOCTrackList] == nil) {
self.SPOCTrackList = [[NSMutableArray alloc]init];
NSLog(#"SPOTTrackList INITIALIZED");
}
In a separate VC, I'm trying to pass/addObject to SPOCTracklist...
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
SCTrack* selectedTrack = self.trackList[indexPath.row];
[[[SPOCVC sharedInstance]SPOCTrackList]addObject:selectedTrack];
NSLog(#"%lu", (unsigned long)[[[SPOCVC sharedInstance]SPOCTrackList]count]);
So my NSMutableArray is initialized and I can add dummy objects, but why can't I pass it from another VC using singleton or anything, such as...
SPOCVC* spocVC = self.tabBarController.viewControllers[2];
[spocVC.SPOCTrackList addObject:selectedTrack];
Thanks for pointing me in the right direction.
View controllers are only intended to be around while they are on screen. They are not a place to store data. Generally when one view controller talks directly to another view controller that it didn't create, you're doing something wrong.
Move SPOCTrackList to your model and have both view controllers talk to it rather than to each other.
There should never be a "sharedInstance" on a view controller. That's a sure sign that you're abusing the view controller as the model.
What's probably happening in your particular case is that viewDidLoad is running on a completely different SPOCVC than your sharedInstance.
why not use appdelegate to handle this
appdelegate.h
//add property to hold the reference
#property (nonatomic, copy) NSMutableArray *referenceArray;
//share the app delegate
+(AppDelegate *)sharedAppDelegate;
#end
in appdelegate.m
//synthesize the property
#synthesize referenceArray;
//return the actual delegate
+(AppDelegate *)sharedAppDelegate {return (AppDelegate *)[UIApplication sharedApplication].delegate;}
in viewdidload method
//add the delegate
import "appdelegate.h"
//init the array
self.SPOCTrackList = [[NSMutableArray alloc]init];
//Add reference
[AppDelegate sharedAppDelegate].referenceArray = self.SPOCTrackList;
and add anywhere like this
import "appdelegate.h"
[[AppDelegate sharedAppDelegate].referenceArray addobject:object];
I have FirstViewController and SecondTableViewController. In SecondTableViewController.m, I create a cell in the cellForRow... method where the cell.textLabel.text is a string from an NSInteger property ("count") of the SecondTableViewController.
I would like a button in FirstViewController to increment the value of count.
I've tried making a property of FirstViewController and then using that:
#property SecondTableViewController *viewController;
and
- (IBAction)buttonTouched:(id)sender {
self.viewController.count++;
[self.viewController.tableView reloadData];
}
But this way isn't working. count is still its original value of zero. I've also reloaded the table in viewWillAppear and still nothing. How can I do this?
Count being used as a property may be where you are going wrong because count is a method that returns the number of objects in an array that is found in foundation framework. Also keep in mind that if you are storing a integer into a string object try storing it in this format.
cell.textlabel.text = [NSString stringWithFormat: #"%i", count];
Hope this helps
Try following
firstViewController.h
#interface DMFirstViewController : UIViewController
#property (nonatomic, strong) DMSecondViewController * secondController;
- (IBAction)buttonPressed:(id)sender;
#end
firstViewController.m
- (IBAction)buttonPressed:(id)sender
{
++self.secondController.count;
[self.navigationController pushViewController:self.secondController animated:YES];
}
secondViewController.h
#property (nonatomic) int count;
secondViewController.m
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
NSLog(#"%d", self.count);
}
EDIT
Check out those two images and implement the similar logic and get the solution.
----- END OF NEW EDIT -----
OLD
I think you haven't assigned and allocated memory for SecondTableViewController reference i.e, self.viewController of FirstViewController in its viewDidLoad method i.e,
-(void) viewDidLoad //In FirstViewController
{
self.viewController = [[SecondTableViewController alloc] init];
}
and pushed the same reference on to the stack of navigationController after performing button taps to increase the count of count variable of SecondTableViewController.
If you are not clear, comment.
I have:
UITableViewControllerA (Lists different ways to refine database product results)
UIViewControllerB (Controller associated with one of the rows from list above)
BaseController (Subclass of UIViewController, contains common code for controllers associated with rows in UITableViewControllerA and is their super class)
I can pass data to UIViewControllerB from UITableViewControllerA easily using the perpareForSegue method. I am now trying to pass data from UIViewControllerB to UITableViewControllerA when popViewControllerAnimated is called in my UIButton custom method that a user taps once they have made a selection of what to refine database product results with.
I decided to use delegation. UIViewControllerB which is subclass of a BaseController and in UITableViewControllerA I mentioned above that I have a list of ways to refine products:
Gender - Size - Colour - Price - Type
Rather than define my protocol below in each of their controllers I decided to define it in the BaseController like so:
#import <UIKit/UIKit.h>
#protocol BaseViewControllerDelegate <NSObject>
- (void)didTapDoneButtonWithSelection:(NSString *)selection; // use string for now to test
#end
#interface BaseViewController : UIViewController
#property (nonatomic, retain) id <BaseViewControllerDelegate> delegate;
- (UIButton *)clearButton;
- (UIButton *)doneButton;
#end
Then I went over to UIViewControllerB and accessed the delegate property and passed in the string to be sent over to UITableViewControllerA when done button is tapped:
- (void)doneButtonTapped:(id)sender {
NSLog(#"DONE BUTTON TAPPED");
[[self delegate] didTapDoneButtonWithSelection:#"THIS IS A TEST"];
[[self navigationController] popViewControllerAnimated:YES];
}
In UITableViewControllerA I conform to the delegate protocol:
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import "BaseController.h"
#interface UITableViewControllerA : UITableViewController <BaseViewControllerDelegate>
#end
In UITableViewControllerA's implementation file I declare an instance var to hold the data passed over from the UIViewControllerB and display in a log message in viewWIllAppear. The didTapDoneButtonWithSelection method gets the data from the argument and stores it in the _test var:
#interface UITableViewControllerA ()
#end
#implementation UITableViewControllerA
{
NSString *_test;
}
- (void)viewWillAppear:(BOOL)animated
{
NSLog(#"%#", _test);
}
- (void)didTapDoneButtonWithSelection:(NSString *)selection
{
_test = selection;
}
Issue:
My log message is giving me (null).
Is what I'm doing possible? or am I going about it the wrong way. If so please correct me with a code example.
Thanks for your time
Regards
I had to grab an instance of UIViewControllerB and set UITableViewControllerA as it's delegate in the prepareForSegue method:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
UITableViewControllerA *tvca = [segue destinationViewController];
[tvca setDelegate:self];
}
However this feels odd, like there is another way I should be doing this.
EDIT:I think initially misunderstood the question.
If you are using segues the correct answer is the other one. If you aren't then it's:
In UITableViewControllerA when you are about to push the BaseViewController it should look something like this:
BaseViewController* base = [[BaseViewController alloc] init];
base.delegate = self;
[self.navigationController pushViewController:base animated:YES];
I'm trying to set the delegate for my custom protocol that has one required method allowing me to pass an array of objects back in the hierarchy of two UITableViewControllers. My delegate continues to return nil. Due to this, my required method is never called.
I'm wondering if the datasource and delegate implementations with my UITableViewControllers is causing a conflict. Also, perhaps ARC is getting in the way when declaring the delegate?
It should be noted that both UITableViewControllers were built using Storyboard and are navigated using segues within a UINavigationController (not sure if this may be causing issues or not).
The nav is --> AlarmViewController --> AlarmDetailsViewController. I create an Alarm object in my AlarmDetailsViewController that contains all the details for an alarm, place it into an array and I want to pass that array back to my AlarmViewController to be displayed in a custom cell in the table.
NOTE: I want to use the Delegate pattern here. I'm not interested in solutions that invoke NSNotifications or use my AppDelegate class.
AlarmDetailsViewController.h
#import "Alarm.h"
#protocol PassAlarmArray <NSObject>
#required
-(void) passAlarmsArray:(NSMutableArray *)theAlarmsArray;
#end
#interface AlarmDetailsViewController : UITableViewController <UITableViewDataSource, UITableViewDelegate>
{
//.....
id <PassAlarmArray> passAlarmsArrayDelegate;
}
#property (nonatomic, retain) id <PassAlarmArray> passAlarmsArrayDelegate;
#end
AlarmDetailsViewController.m
#import "AlarmDetailsViewController.h"
#interface AlarmDetailsViewController ()
#end
#implementation AlarmDetailsViewController
#synthesize passAlarmsArrayDelegate;
-(void) viewWillDisappear:(BOOL)animated
{
NSLog(#"delegate = %#", self.passAlarmsArrayDelegate); // This prints nil
[[self passAlarmsArrayDelegate] passAlarmsArray:alarmsArray];
}
//....
#end
AlarmViewController.h
#interface AlarmViewController : UITableViewController <UITableViewDataSource, UITableViewDelegate, PassAlarmArray>
{
//...
AlarmDetailsViewController *alarmDetailsViewController;
}
#property (nonatomic, retain) AlarmDetailsViewController *alarmDetailsViewController;
#end
AlarmViewController.m
#import "AlarmViewController.h"
#import "AlarmDetailsViewController.h"
#import "AlarmTableViewCell.h"
#import "Alarm.h"
#interface AlarmViewController ()
#end
#implementation AlarmViewController
#synthesize alarmDetailsViewController;
- (void)viewDidLoad
{
[super viewDidLoad];
// This is where I'm attempting to set the delegate
alarmDetailsViewController = [[AlarmDetailsViewController alloc]init];
[alarmDetailsViewController setPassAlarmsArrayDelegate:self];
}
//....
//My #required protocol method which never gets called since my delegate is nil
-(void) passAlarmsArray:(NSMutableArray *)theAlarmsArray
{
alarmsTableArray = theAlarmsArray;
NSLog(#"alarmsTableArray contains: %#", alarmsTableArray); // Never gets called due to delegate being nil
NSLog(#"theAlarmsArray contains: %#", theAlarmsArray); // Never gets called due to delegate being nil
}
#end
I've attempted to set the delegate in a method that fires when a button is pressed in AlarmViewController (as opposed to the viewDidLoad method) but that does not work either.
I'm assuming I've got a logic flow error somewhere here . . . but nearly 2 days of hunting and rebuilds haven't uncovered it. Ugh.
You're setting your delegate in the wrong place, and on a different instance of the controller than the one you will get when you do the segue. You should set the delegate in the prepareForSegue method if you're pushing AlarmDetailsViewController from AlarmViewController
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
AlarmDetailsViewController *alarm = segue.destinationViewController;
alarm.passAlarmsArrayDelegate = self;
}
You really need to understand the life cycle of view controllers, how and when they're instantiated, and when they go away. This is the very heart of iOS programming, and Apple has extensive documentation on it. Reading up on segues would also be very useful. A segue (other then an unwind segue) always instantiates a new instance of the destination controller. So, when your segue is performed, whether directly from a button, or in code, a new (different from the one you alloc init'd directly) details controller is instantiated. Before that segue is performed, prepareForSegue: is called, and that's when you have access to the one about to be created. That's the place to set a delegate or pass any information on to the destination view controller.
Did you try replace (nonatomic, retain) with (nonatomic, strong) since you are using ARC?
Auto-synthesized properties like your alarmDetailsViewController property have backing ivars prefixed with underscores, e.g. _alarmDetailsViewController. Your alarmDetailsViewController ivar (the alarmDetailsViewController declared inside the #interface ... {} block in AlarmViewController.h) is different from the backing ivar of your alarmDetailsViewController property.
Just delete your alarmDetailsViewController ivar and use the #property, preferably through self.alarmDetailsViewController.