I have a UIViewController subclass that will itself be subclassed into many custom UIViewControllers. It contains a method to check authentication info, and if the authentication fails, it should segue to a particular view. I am looking at making use of UIStoryboardSegue's *"segueWithIdentifier"* method for this purpose. The question is, what do I specify for the destination parameter, i.e. how do I get the UIViewController instance pertaining to my desired destinationviewcontroller?
I afraid it's not that easy because ever subclass of your view controller could go to different view controller and if you want to do it via segue all of that segue will be different. I think the best solution is let your child view controller decide which segue to fire (which view present/push).
Add your check authentication method like that to the parent view controller
-(void)checkAuthentication
{
if (userAuthenticated)
{
[self userAuthenticatedMethod];
}
else
{
// if you want to go to the same view controller if user not authenticated you can
// perfoem segue like that:
[self performSegueWithIdentifier:#"failedSegue" sender:nil];
// but if it depends on the view controller you are in do it like that
[self userNotAuthenticatedMethod];
}
}
Add declaration of this method to .h file and put empty implementation to .m file:
//in .h
-(void)userAuthenticatedMethod;
//just if you need it
//-(void)userNotAuthenticatedMethod;
//in .m
-(void)userAuthenticatedMethod
{
//override in child
}
//just if you need it
//-(void)userNotAuthenticatedMethod
{
//override in child
}
Now in every child view controller you need to implement userAuthenticatedMethod method and if needed userNotAuthenticatedMethod.
If you want to use segue just do something like that:
-(void)userAuthenticatedMethod
{
[self performSegueWithIdentifier:#"yourSegue" sender:nil];
}
You can also add view controller to view hierarchy programatically. In this scenario each child view controller is responsible to add another view to the view hierarchy.
If you need pass the data you can override prepareForSegue: method in every child VC you want.
Related
I have a UISwith in a viewcontroller A. I want to pass the state of the UISwitch to viewcontroller B. I am using Objective C. This is the code I'm using in viewcontroller A:
if (![switchview isOn]) {
NSLog(#"OFF");
} else {
NSLog(#"ON");
[comment setObject:#"YES" forKey:kESActivityPrivateKey];
}
I need to save the value of the UISwitch in viewcontroller B.
Any help is appreciated.
Don't try to access another view controller's view hierarchy directly. You should treat another view controllers views (and a switch is a kind of view) as private.
Set an IBAction on your switch that points to the owning view controller. In that action save the sate of the switch to a property of your view controller.
You can then access the property from the other view controller.
I have a UITableViewController and custom UITableViewCell, when I tap some button on the cell, controller should push to another controller and present another view.
I can think of two solutions for the code architect for this.
First one is that I create a protocol method to react to the tapping event in my cell and set controller as delegate, so once there is tapping on my cell, controller would react to push to another view.
But I could also do something in my cell class like this, instead of creating delegate, I keep asking the nextResponder if it's the right controller with for loop and once I get it I use it to push to the next one:
#implementation MyCustomTableViewCell
//...
//...
//...
-(void)tappedOnSomeView
{
id obj = nil;
for (obj = self; obj; obj = [obj nextResponder]) {
if ([obj isKindOfClass:[MyTableViewController class]])
{
UIViewController *uiVC = (UIViewController *)obj;
MyNextViewController *nextVC = [[MyNextViewController alloc] init];
[uiVC.navigationController pushViewController:nextVC animated:YES];
return;
}
}
}
//...
//...
//...
#end
So is this not very MVC? Or is it ugly code? Should I just create delegate to handle all the gesture events on my cell in its tableview controller? Or is there another better way to do this?
Thanks.
You should go with the first approach. The benefit of this is that you can also pass back some data from the cell back to the view controller.
The other option is you could do something like this
In cellForRowAtIndexPath:, add target to the view to handle the tap event.
This way you can directly catch the response for the tap inside the view controller. However I would emphasise on the 1st approach of delegate.
Having user actions on custom table view cells is a common practice and per MVC, "view" should not take decisions like what to show, how to show, when to show. View should only know what things it needs to draw on what conditions. In your case, per MVC, first approach makes sense. Add your ViewController as delegate to cell in cellForRowAtIndexPath: and handle the pushing of new view controller in your controller!
This is puzzling me.
The context
The original tutorial I'm following.
Where the segue is added to the Main View via a custom segue:
- (void) perform {
MainViewController *source = (MainViewController *)self.sourceViewController;
UIViewController *destination = (UIViewController *) self.destinationViewController;
for(UIView *view in source.main.subviews){
[view removeFromSuperview];
}
source.currentViewController = destination;
destination.view.frame = CGRectMake(0, 0, source.main.frame.size.width, source.main.frame.size.height);
[source.main addSubview:destination.view];
}
The TextField is connected as delegate in the child View Controller. All things being equal I get the app crashed without any message.
The workaround
In the Main View Controller, in -(void)prepareForSegue: I've added [segue.destinationViewController setDelegate:self]; in the meantime I've added a property in the child View Controller id<UITextFieldDelegate> delegate and modified the textfield delegate as self.delegate.
This works, but the trouble is that I've to set the delegated methods in Main View Controller which is not quite efficient as I have more View Controllers to add.
The Objective
How do I set each View Controller to be the delegate for itself without crashing?
The immediate cause of your error is that the view controller that your views belong to is being deallocated. The fact that your views are on screen while their view controller is deallocated highlights a fundamental flaw in the approach of taking views off one view controller and adding them to another. View controller containment is the correct way to solve an issue like this.
Changing the currentViewController property to strong will fix the memory management issue you're seeing, but it's just a bandaid. Your currentViewController will still be missing rotation methods, appearance and disappearance methods, layout methods, and so forth. View controller containment ensures these methods get called for the view controller whose views are on screen.
Here is an altered version of your project that illustrates how to use view controller containment. I think that will be a better solution than manually removing and adding subviews of the view controllers themselves. See the Apple docs for more info on custom view controller containers.
At first, let's see crash report. Please, do the following:
1. Add Exception Breakpoint
2. Edit it as in the picture
You should create a custom class for the destinationViewController wich will implement UITextFieldDelegate
#interface DestinationViewController <UITextFieldDelegate>
#end
And from storyboard add the class to UIViewController that has TextField
And make the connections for elements and TextField delegate.
Implement delegate methods.
You will not need the implementation of prepareForSegue: anymore. You will have two different classes with different elements. Only if you need to pass something from source to destination then you use prepareForSegue:
Hope you'll understand
Within my viewDidLoad I would like some custom code based upon the previous controller.
How can I access the segue source controller or the previous segue identifier in the destination controller's viewDidLoad to handle this?
There is no way to get a reference to the segue that created you. You could create a property (sourceVC in my example) in the destination controller, and assign self to this property in the prepareForSegue method (in the source view controller):
[(DestinationVCClass *)segue.destinationViewController sourceVC] = self;
You can just use [self presentingViewController] and you'll be able to access the VC that issued the segue. I usually like to couple it with isMemberOfClass: for a situation like this.
You do the following in the unwinding segue method in the destination
self.source = (UIStoryboardSegue *)segue.sourceViewController;
Define source as a UIStoryboardSegue in the destination. The above line will give the source or the previous segue.
I've been converting an application to use storyboards. I'm sure this is a simple problem, but somehow I can't seem to figure out the 'correct' way of doing it, coming as we are from the old XIB world.
One of the subsections of it contains a UITabBarController, each with some subviews within it.
The action that launches this set of tabs works perfectly; I detect the segue, and set some data properties within my (custom) UITabBarController.
Next, I would like to be able to pass that data to the child views when they get created. But - because these tabs are simply 'relationships' and not segue's, I can't do what I do everywhere else, which is override the 'prepareForSegue' function.
In the old XIB universe, I'd simply bind some IBOutlets together between the tab controller and the child views. But I can't do that in storyboards, because the parent and children are separate 'scenes'.
I've tried making my UITabBarController class implement its own delegate, override 'didSelectViewController' and doing 'self.delegate = self' which almost works, except for the fact that it is never called with the first tab when the view is initially shown.
What's the "correct" (or 'best') way to do this? Please don't tell me to get/set some value on the app delegate, as this is 'global variable' territory - nasty.
Try looping through the view controllers on the UITabBarController, e.g. in this example the setData method is called from the segue in to the UITabBarController, and it then loops through the child view controllers, making a similar call on the child controller to set the data on that too;
- (void)setData:(MyDataClass *)newData
{
if (_myData != newData) {
_myData = newData;
// Update the view.
[self configureView];
}
}
- (void) configureView {
for (UIViewController *v in self.viewControllers)
{
if ([v isKindOfClass:[MyDetailViewController class]])
{
MyDetailViewController *myViewController = v;
[myViewController setData:myData];
}
}
}