Can't get passed data in viewDidLoad method - ios

I'm trying to pass some data to my view controller class like this:
MyViewController *vc = [[MyViewController alloc] init];
vc.myProperty = dataToBePassed;
[self.navigationController pushViewController:vc animated:YES];
I need to make some view configuring in viewDidLoad, but it seems that viewDidload called earlier than property assignment.
Then in MyViewController implementation:
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(#"%#", self.myProperty); // Here i get myProperty = nil
}
- (void)viewWillAppear
{
[super viewWillAppear];
NSLog(#"%#", self.myProperty); // Here i get myProperty = dataToBePassed but it's to late
}
How can i get passed data in viewDidLoad method without implementing singleton or delegate patterns?

Try doing this
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:
#"MainStoryboard" bundle:[NSBundle mainBundle]];
MyViewController *vc = [storyboard instantiateViewControllerWithIdentifier:#"storyboardIdentifier"];
vc.myProperty = dataToBePassed;
[self.navigationController pushViewController:vc animated:YES];
You have to set a storyboard identifier first in the storyboard for the view controller.

While the code sample you provide looks technically correct, I'm with #john-elemans in that you need to show more code.
There is something that is referencing the view which causes it to load and therefore causes viewDidLoad to fire prematurely.
In any case, if something (such as your property) is absolutely essential to the correct building of your view structure, I'd put in its own designated initializer, e.g.,
- (id)initWithPhotoDiameter:(CGFloat)diameter
{
self = [super init...]; // some VC initializer that you should call
if (self) {
_photoDiameter = diameter;
}
return self;
}
Notice the use of the backing instance variable _photoDiameter instead of self.photoDiameter. This is about the only place in a class where you should use the backing ivar, since self is still in the process of being initialized.

Technically there are two approaches that are quite common for lifecycle handling of view controllers related to an application.
Using XIBs
When using XIBs one of the most common if not the most common process to create and setup your view controllers is done programmatically. Following this process, when you initialise the view controller you have the option of either overriding your init method in order for your view controller to have the information prior to loading the view and easing up the process of adjusting drawn content. You can also create a method within your view controller to be called in which you pass the data to be used by the view controller.
Using Storyboard
If you are using storyboards I recommend that you trust segues setup through it. I have found that they make life easier and it will allow you to use certain methods to handle the transition. One of those is prepareForSegue:sender: Within that method I have found that it is easier to setup a view controller after it's initialized accessing the destination controller. You also might consider having all data there before viewDidLoad hence following the segue approach.

Related

Is there any way to avoid calling viewdidload method like tabbarcontroller?

I'm developing an application which will work based on maps. So once user opens MapViewController then I will load some data every 5 seconds.
I'm using navigation controller(Push view controller).
So every time when user goes to MapViewController viewdidload method calling. I don't want like that.
That's why I'm trying to avoid viewdidload method like tabbarcontroller.
Is there any way to achieve this?
viewDidLoad is getting called because your MapViewController is getting deallocated when you pop it off of the top of your navigation controller. When you recreate the view controller, it's getting allocated all over again, and the view loads again. If you keep a reference to MapViewController in the class containing your navigation controller, then ARC will not deallocate the object, and you can use this reference to push it back onto the stack so viewDidLoad will not get called again.
Edit: Adding code for reference:
MapViewContoller *mapViewController; // declared globally so there's a strong reference.
- (void) openMapViewController {
if (!mapViewController) {
mapViewController = [self.storyboard instantiateViewControllerWithIdentifier: MapViewControllerID];
}
[self.navigationController pushViewController: mapViewController, animated: YES];
}
Try this
-(void)clickForPush{
// declarre viewcontroller e.g 'saveRecipeVC' instance globally in interface
if (!saveRecipeVC) {
saveRecipeVC = [self.storyboard instantiateViewControllerWithIdentifier:SaveRecipeVCID];
}
[self.navigationController pushViewController:saveRecipeVC animated:YES];
}
viewDidLoad is intended to use when,not possible or efficient to configure 100% of an interface in a XIB. Sometimes, a particular property you wish to set on a view isn't available in a XIB. Sometimes, you use auto layout, and you realize that the editor for that is actually worse than writing auto layout code. Sometimes, you need to modify an image before you set it as the background of a button.
If you dont want to do these things make your viewDidLoad empty. Than avoiding. Or
Add code conditionaly into your viewDidLoad.
(void)viewDidLoad {
[super viewDidLoad];
if(condition) {
// put your code here
}
}

Unable to pass data from one child viewcontoller to another childviewcontoller

I am using this to implement ContainerViewContoller. Everything is going fine but I'm unable to pass data from one child ViewController to an other child ViewContoller.
CouponCodeViewController *couponVC = [[UIStoryboard storyboardWithName:#"Main" bundle:nil] instantiateViewControllerWithIdentifier:#"CouponCodeViewController"];
couponVC.coponcode=#"this is data";
couponVC.title = #"Enter Coupon Code";
[couponVC viewWillAppear:true];
CategoryViewController *categoryVC = [self.storyboard instantiateViewControllerWithIdentifier:#"CategoryViewController"];
categoryVC.title = #"Choose A Category";
YSLContainerViewController *containerVC =
[[YSLContainerViewController alloc]
initWithControllers:#[couponVC,categoryVC]
topBarHeight:statusHeight + navigationHeight
parentViewController:self];
When I call my container ViewController it loads all childVC at the same time. Now I want to pass data on click from CouponCodeViewController to CategoryViewController but I'm not able to do so because viewDidLoad, viewWillappear, and viewDidAppear are not called
CouponCodeViewController I am going using this:
- (IBAction)skipAct:(id)sender {
// CategoryViewController *category=[[CategoryViewController alloc]init];
// category.userInput=#"this is the data";
[self.scrollMenuDelegate scrollMenuViewSelectedIndex:1];
}
#pragma mark -- YSLContainerViewControllerDelegate
- (void)containerViewItemIndex:(NSInteger)index currentController:(UIViewController *)controller
{
[controller viewWillAppear:YES];
}
In CategoryViewController my viewDidLoad and viewDidAppear are not called.
How can I pass data from childVC to another ChildVC.
That because when you init all controllers, the parent controller doesn't do 'addChildViewController'. It does that only when showing a new controller (I've looked the code).
And after it move from this controller, it removes it from the controller hierarchy.
I would use either delegate or notification to pass the data.
Another option (which I don't like) is to give the coupons controller a reference from the categoryViewController.

Setting delegates (for protocols) only works in prepareForSegue?

Most of the information I found involving implementing protocols and delegates involves a step where you do this;
DestinationViewController *destinationVC = [[destinationViewController alloc] init];
destinationVC.delegate = self;
But after hours of frustration because I couldn't get it to work I finally stumbled across another way to allocate the destinationVC in prepareForSegue
DestinationViewController *destinationVC = segue.destinationViewController;
destinationVC.delegate = self;
Which actually works. What was I doing wrong? It seemed using the first method my delegate was never set to self.
When instantiated from a storyboard, the initWithCoder: methid is called, not the init method.
DestinationViewController *destinationVC = [[destinationViewController alloc] init];
destinationVC.delegate = self;
is how you do when your controller is not from a storyboard: you init it from the code. After that you have to manually handle the transition from your source VC to your destination VC.
DestinationViewController *destinationVC = segue.destinationViewController;
destinationVC.delegate = self;
is how you do when your controller is defined in a storyboard and is the destination of a segue.
When you perform a segue, the prepareForSegue: method of the source view controller is called, in which you should configure your destination like you want: setting properties, delegates, passing data,...
There is two way you can pushController while using UIStoryboard.
Option 1 : taking reference of actually UIViewController from storyboard.
UIViewController *displayTable = [self.storyboard instantiateViewControllerWithIdentifier:#"nextViewcontroller"];
[self.navigationController pushViewController:displayTable animated:YES];
Option 2 : Using Segue
[self performSegueWithIdentifier:#"MySegue" sender:sender];
In your first case you are allocating object and assign delegate. that does't means while performing pushViewController operation same reference is passing. so in that case two different reference is created. so you delegate is point out some other reference that doesn't exist.
may this help you.
Here is the basic tutorial about segues, updated for Xcode 6 and above.
https://developer.apple.com/library/ios/recipes/xcode_help-IB_storyboard/chapters/StoryboardSegue.html
When you use storyboards, all necessary context provided by UIStoryboardSegue class. it holds destination view controller for you. So, you must access destination controller throw destinationViewController property.
if you want to manually add controller to your navigation stack:
{
// binds your viewController from storyboard with local instance
UIViewController *vc = [self.storyboard instantiateViewControllerWithIdentifier:#"YOUR_STORYBOARD_IDENTIFIER"];
// set your delegate
vc.delegate = self;
// push controller into navigation stack
[self.navigationController pushViewController:vc animated:YES];
}

Can I invoke the delegate method after popViewcontroller?

1) I have two controllers, fistViewController, secondViewController.
2) first controller implements the delegate say "xyzDelegate".
#interface FirstViewController : UIViewController <xyzDelegate>
3) The delegate method in First View Controller refreshes the UIViewTable.
4) First Controller : Pushing Second View Controller.
SecondviewController *svc = [[SecondViewController alloc] initWithNibName:#"SecondViewController" bundle:nil];
svc.delegate = self;
[self.navigationController pushViewController:svc animated:YES];
4) In second View Controller:
id<xyzDelegate> strongDelegate = self.delegate;
[self.navigationController popViewControllerAnimated:YES];
NSLog (#"After popViewControler");
[strongDelegate dateSelected:dateChoosen]; // Invoking Delegate Method.
Question:
1) Is it the general practice to invoke the delegate method after Popping View Controller?
as i am refreshing the UITable, once the delegate method is invoked in FirstView Controller.
Usually, a view controller is an independent unit of screens. Especially if it's switched by navigation-controller. You are expected to reconfigure views to bind their data in one of overriding of viewWillAppear: or viewDidAppear: method.
Usually viewDidAppear: is preferred. Because in many cases, switching view needs reloading of underlying data, and this usually causes asynchronous I/O. In this case, this asynchronous I/O may interfere simultaneously performing view-switching animation.
Anyway, if your view setup operation is lightweight, it's fine and better to go with viewWillAppear: because it will make your user to wait less.
In this case, IMO, it seems your best bet is just marking to refresh the data on the target view controller, and handle refreshing in the view-controller's viewDidAppear: method.
You should call the delegate methods BEFORE POP action occurs.
[strongDelegate dateSelected:dateChoosen];
popViewControllerAnimated Will call second view controller dealloc method to destroy, where you would release the strongDelegate. So no more strongDelegate to receive the dateSelected: method.
[self.navigationController popViewControllerAnimated:YES];

UnitTests and iOS : viewDidLoad triggered twice

I am testing the viewDidLoad event on one of my UIViewController.
- (void)testMyView
{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
MyViewController *vc = [storyboard instantiateViewControllerWithIdentifier:MYID];
[vc viewDidLoad];
STAssertNotNil(vc, #"MyViewController should not be nil");
}
If I remove the line [vc viewDidLoad];, the viewDidLoad is never triggered.
If I let [vc viewDidLoad]; in place, the viewDidLoad is triggered twice.
I understand that views are lazy loaded, but how can I avoid that behavior?
Is there any best practice regarding View testing?
You need to access the view in order to have it load automatically.
You can use something like this to do it without side effects:
vc.view.hidden = NO; // Or YES if it is supposed to be hidden.
Oh, and then remove your manual call to viewDidLoad as it won't be needed.
Read my note https://github.com/onmyway133/blog/issues/52
Suppose we have the following view controller
class ListController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
}
}
Get to know viewDidLoad
We know that viewDidLoad is called when view is created the first time. So in the the Unit Test, if you use viewDidLoad to trigger, you will fall into a trap
func testSetup() {
let controller = ListController()
controller.viewDidLoad()
}
Why is viewDidLoad called twice?
It is called once in your test
And in your viewDidLoad method, you access view, which is created the first time, hence it will trigger viewDidLoad again
The correct way
The best practice is not to trigger events yourself, but do something to make event happen. In Unit Test, we just access view to trigger viewDidLoad
func testSetup() {
let controller = ListController()
let _ = controller.view
}
Do lazy loading using [vc view];
Call layoutIfNeeded() on the view controller's view, namely:
[vc.view layoutIfNeeded]
You can only use the following call to execute the viewDidLoad only once:
_ = vc.view

Resources