Is writing controller code in a view class not very MVC? - ios

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!

Related

Xcode - Segue from UIImageView to new ViewController

Simple question that I'm currently having trouble as to how to get started.
I have a ViewController with multiple images/icons, and I would like to have new view controllers for each image/icon that is selected. I have a segue (show) from my initial view controller to my new one...but now how do I code it so when I click on a specific image it'll segue to the corresponding VC I want?
Thanks in advance!
If all of your Images/Links goes to one kind of ViewController, you can link it with segue in your interface builder and so you use just -performSegueWithIdentifier and seed it with your data or model corresponding to your ViewController.
For getting touch events and handling it, you can use delegate method which each subviews call their delegate when touch event catches. Assume below code for better imagination:
//Added in initiation of SubView
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapGestureUpdated)];
//This function will be fired when gesture recognized
- (void)tapGestureUpdated {
if ([self.tapDelegate respondsToSelector:#selector(onItemTapped:)]) {
[self.tapDelegate onItemTapped:self.item];
}
}
But if you're showing to different types of ViewControllers, for each kind you should add segue and ViewControllers.
Superview will call next ViewController like this:
[self performSegueWithIdentifier:#"showNextViewControllerSegue" sender:self];
So there is such event chain for handling it:
UITapGestureRecognizer -> Your SubView -> Call it's delegate
(SuperView) -> Call next segue according to subView's type.

iOS - Segue with variable source / destination

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.

Get the UIViewController in TableView's CustomCell

I have one UIViewController named "MainView" and that has one UITableView named "tblLists"
tblLists generating the customCell - "customCellList".
My question is can I get the MainView's instance(self) in customCellList class.
I tried with superview thing but not get MainView. I want to achieve this without protocol.
So need your help in this.
You can use the responder chain to gain access to the view controller. Assuming your customCell class is a UITableViewCell subclass, the following method should do the job:
#implementation customCell
- (UIViewController *)getViewController
{
id vc = [self nextResponder];
while(![vc isKindOfClass:[UIViewController class]] && vc!=nil)
{
vc = [vc nextResponder];
}
return vc;
}
#end
The above code is courtesy of the Sensible TableView framework.
What you are asking is not a good idea, you should find another way around. It breaks the MCV pattern.
By the way, if you are worried with memory concern using ARC and targeting iOS>=5 you can create a weak reference to the table view itself and get the view controller as its delegate or data source property (of course if the VC is one them). Or you can create a weak reference to the VC itself.
As pointed in the comments is not a good idea, better find another way around. If you need to update you cells value there are a lot of methods to reload tableview data! By means of using KVO, notification, delegation etc on your VC from the model, you can simply trigger a reload to the table view without involving weird references in cells.
Hope this helps.
For those rare times when you want to break MVC.. This assumes you are using a Navigation controller as the rootVC on your window. Updated for Swift 2
func visibleVC() -> UIViewController? {
if let navVC: UINavigationController = UIApplication.sharedApplication().keyWindow?.rootViewController as? UINavigationController {
if let vc: UIViewController = navVC.visibleViewController as? MyViewControllerClass {
return vc
}
}
return nil
}
Also you can access to rootViewController:
UIViewController *controller = [UIApplication sharedApplication].keyWindow.rootViewController;
I have similar question, my case is to change array in View Controller when the value of textfield in Table View custom cell changed.
My solution is add delegate for UITextFiled in cellForRowAt method of tableview, then I can do all my data changing in textFieldDidEndEditing method. Because they are all in one class, the ViewController Class.

how to create a uiview with segues, which is used by multiple UIViewControllers in storyboard

Basically what I want to do is to include UIView to multiple UIViewControllers on storyboard. I could include the uiview, but segues in the UIView doesn't work.
I have a storyboard something like this:
I have a tab controller with 2 UIViewControllers First and Second. And I have a separated UITableViewController with two another UIViewControllers A and B, connected with segues.
I could add the table view into First and Second views as a subview, but when I tap cell it doesn't go to next screen A or B. I sort of figured out why it didn't work, but just can't figure the best way to accomplish this.
Is there any good way to do this? I'm new to storyboard but have been developing iOS app for a while.
EDIT:
The way I add the tableViewController to each View is following:
self.tableViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"theTableViewController"];
[self.view addSubview:_tableViewController.tableView];
When a cell in the table view is tapped, prepareForSegue:segue:sender is invoked, but no push to navigation controller since the table view controller is sitting in each view controller as just a subview
EDIT2:
I posted my test project here
There are 2 methods you can try .
1.Create a segue between the tableview cell and next view controller directly. What I want to emphasize is the fact that do not create segue between the tableview or view and the next view controller,unless trying the method below.
2.Add this code to your tableview controller :
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([indexPath row]) {
[self performSegueWithIdentifier:#"segueToB" sender:self];
}else {
[self performSegueWithIdentifier:#"segueToA" sender:self];
}
}
I finally figured out how to make segue within sub views. It was actually very simple. It seems that I need to add the view controllers to the main view controller as a "childViewControllers"
instead of
self.tableViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"theTableViewController"];
I did like this:
[self addChildViewController:[self.storyboard instantiateViewControllerWithIdentifier:#"theTableViewController"];
[self.view addSubview:_tableViewController.tableView];
and segues in the childViewController work as I expect. Thank you guys for your help.

Passing data between views with a UITabBarController when using storyboards

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];
}
}
}

Resources