Get the UIViewController in TableView's CustomCell - ios

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.

Related

Extending ViewController from Storyboard

I've this situation when I've decided to reuse and extend one of my AViewController which also extends UITableViewController with its view in UIStoryboard A.
So I've created a new protocol. BViewController which extends AViewController in the other UIStroryboard. I've added other UITabelViewController with class BViewController, Cell creation and all the other tableViews is on AViewController side. In the extended view I want to provide a new source of data for TableView.
I thought, since I'm extending existing VC with view. I don't have to recreate IBOutlets like tableView, cell etc. in a new view.
But at this point it seams that I should recreate a view in the other Storyboard? I'm I missing something?
The content of the view hierarchy for a scene in your storyboard is not inherited by another scene regardless of if you use the same subclass of UIViewController or not. If you don't want to recreate the view hierarchy in your new scene, then you may need to put your view hierarchy in a .xib file and use UINib to do manual nib loading.
You may try this solution.
if let destinationVC = storyboard.instantiateViewController(withIdentifier: "AViewControllerIdentify") as? AViewController {
/// Runtime to set new sub viewcontroller class.
object_setClass(destinationVC, BViewController.self)
if let vc = destinationVC as? BViewController {
/// add you logic here.
}
}

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

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!

iOS how present Modal ViewController from Uiview

i have subclass a UIView and now i need to show a view controller but UIView not have method to present view controller.
this is my problem
thank's
this is a piece of code inside my uiview subclass
-(void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
if ([tabella isEqualToString:#"Articoli"]) {
NSDictionary *itm=(NSDictionary*)[comanda objectAtIndex:indexPath.row];
Articoli *aboutViewController = [[Articoli alloc] initWithNibName:#"Articoli" bundle:nil];
aboutViewController.modalPresentationStyle = UIModalPresentationFormSheet;
aboutViewController.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
aboutViewController.idarticolo=[itm objectForKey:#"idarticolo"];
CGRect aboutSheetFrame = aboutViewController.view.frame;
UIViewController *viewController = [UIViewController new];
viewController.view = self;
//here xcode give me a red error
[self presentViewController:viewController animated:YES completion:nil] ;
aboutSheetFrame =CGRectZero;
aboutViewController.view.superview.bounds = aboutSheetFrame;
}
}
When you need a communication between UIView instance and UIViewController, there are a few known iOS concepts, which you should adhere to. As you have figured out that UIView cannot really present a new controller (missing either presetViewController:animated:completion methods or navigationController property, which are both present in UIViewController).
Views are supposed to be the most reusable parts of your code, so you must think of a way to design your views to be completely blind to where they are at. They usually only know about user interaction.
So first, what you must do is refactor your view.
If your UIView is supposed to be a UIControl (has some kind of target selectors), you need to use add target in your controller to get callback from view interaction.
You can use delegate pattern as used in UITableView or UICollectionView, which is designed as a protocol.
You can use gesture recognizers added to a view (UITapGestureRecognizer for example), so the controller knows about user interaction.
You can even mix and match those architectural patterns.
But you should really look into iOS programming basics, to understand this better.
In addition the first error I see in your code is that you create a generic UIViewController, when you should really be creating custom subclasses of it, defined in Storyboard and separate subclass of UIViewController.
The second error I see is that your UIView responds do tableView:didSelectRowAtIndexPath: method, which should in fact never happen. All this code must be moved back to one UIViewController subclass.
You can do this without any view hierarchy issues using the below code.
ObjectiveC
UIViewController *currentTopVC = [self currentTopViewController];
currentTopVC.presentViewController.........
- (UIViewController *)currentTopViewController
{
UIViewController *topVC = [[[[UIApplication sharedApplication] delegate] window] rootViewController];
while (topVC.presentedViewController)
{
topVC = topVC.presentedViewController;
}
return topVC;
}
Swift
var topVC = UIApplication.sharedApplication().keyWindow?.rootViewController
while((topVC!.presentedViewController) != nil){
topVC = topVC!.presentedViewController
}
topVC?.presentViewController........
PresentViewController is method of UIViewController class not of UIView, you can do one thing, create UIViewController instance and set its view to the view you have and then present it.
Something like below
YourCustomView *customView = [[YourCustomView alloc]initWithFrame:someFrame];
UIViewController *viewController = [UIViewController new];
viewController.view = customView;
//From currentViewController present this
[self presentViewController:viewController animated:YES completion:nil] ;
Customize this code as per your requirement
But as you are in view you need to pass this event to viewController, so better implement delegate method and at place where you calling present viewController call delegate which is implemented in ViewController and in side that presentViewController with customView set to its view property
You can also present your viewcontroller from the navigation controller object
Create Global Navigation Object in App Delegate or anywhere, you can access navigationcontroller object from view
#property (strong, nonatomic) UINavigationController *gblNavigation;
//Present viewcontroller from NavigationController object
[gblNavigation presentViewController:YOUR_VC_Object animated:YES completion:nil];
You can't present a view controller from a view. You can only present a view controller from a view controller.
Apple wants views to be dumb. That is views should only know how to display content. View should not respond to user interaction: that should be passed to a view controller.
You may want to consider using a delegate pattern, target action, or something similar to allow a view controller to control the interaction.
iOS 15, compatible down to iOS 13
Based on Shamsudheen TK solution for anyone who comes across this question in the future.
let presentedWindow = UIApplication.shared.connectedScenes.flatMap { ($0 as? UIWindowScene)?.windows ?? [] }.first { $0.isKeyWindow }
guard let currentViewController = presentedWindow?.rootViewController else {
return
}
currentViewController.present(UIViewController(nibName: nil, bundle: nil), animated: true)
Note that connectedScenes is available only since iOS 13. If you need to support earlier versions of iOS, you have to place this in an if #available(iOS 13, *) statement.

Instantiate View Controller from Storyboard vs. Creating New Instance

What is the functional difference between instantiating a View Controller from the storyboard and creating a new instance of it? For example:
#import "SomeViewController.h"
...
SomeViewController *someViewController = [SomeViewController new];
versus
#import "SomeViewController.h"
...
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle: nil];
SomeViewController *someViewController = [storyboard instantiateViewControllerWithIdentifier:#"SomeViewController"];
In either case, is someViewController effectively the same thing?
The main difference is in how the subviews of your UIViewController get instantiated.
In the second case, all the views you create in your storyboard will be automatically instantiated for you, and all the outlets and actions will be set up as you specified in the storyboard.
In the first, case, none of that happens; you just get the raw object. You'll need to allocate and instantiate all your subviews, lay them out using constraints or otherwise, and hook up all the outlets and actions yourself. Apple recommends doing this by overriding the loadView method of UIViewController.
In the second case, the view controller will load its view from the storyboard and you will be happy.
In the first case, it won't. Unless you've taken other steps (like overriding loadView or viewDidLoad or creating a xib named SomeViewController.xib), you'll just get an empty white view and be sad.
In Swift you can do the same with,
var someVC = self.storyboard?.instantiateViewControllerWithIdentifier("SomeViewController") as! SomeViewController
You will need to give the Identifier in the Storyboard to the SomeViewController and tick the checkmark to Use Storyboard ID
It is not the same thing. In the storyboard you probably have some UI elements laid out. They might have constraints and properties setup through the storyboard. When you instantiate the viewcontroller via the storyboard, you are getting all the instructions for where those subviews are and what their properties are. If you just say [SomeViewController new] you are not getting all the instructions that the storyboard has for the view controller.
A nice test will be to add a UIViewController to a storyboard and drag a red view onto it. Instantiate it using both methods and see what the differences are.
simple swift 3 extension
fileprivate enum Storyboard : String {
case main = "Main"
}
fileprivate extension UIStoryboard {
static func loadFromMain(_ identifier: String) -> UIViewController {
return load(from: .main, identifier: identifier)
}
static func load(from storyboard: Storyboard, identifier: String) -> UIViewController {
let uiStoryboard = UIStoryboard(name: storyboard.rawValue, bundle: nil)
return uiStoryboard.instantiateViewController(withIdentifier: identifier)
}
}
// MARK: App View Controllers
extension UIStoryboard {
class func loadHomeViewController() -> HomeViewController {
return loadFromMain("HomeViewController") as! HomeViewController
}
}
In case you don't want to instantiate a new VC using instantiateViewControllerWithIdentifier but accessing the instance created by the storyboard from the AppDelegate:
create a property in AppDelegate.h so it will be accessible from classes using it
#property (nonatomic, strong) myViewControllerClass*vC;
in viewDidLoad inside myViewControllerClass.m I access the shared instance of AppDelegate and feed the property with self: [AppDelegate sharedInstance].vC = self;
I had to use this solution in a complex storyboard and still can't get over the fact that I cannot find an easy way to access all (or at least the ones I need) objects in storyboard simply by addressing their identifiers.
another thing to check for is if the viewcontroller that's throwing the error has a storyboardIdentifier, you can check the storyboard xib file.
the identifier was missing in my case, the error stopped when i added it

Send UITableView row From One tableviewController to another

I want to send a UITableViewCell at indexPath.row from one controller to another. I can remove the row using removeObjectAtIndex, but unable to send the removed row into another controller.
I'm trying to store the removed row in an NSMutableArray in one controller, but don't know how to populate it in another controller.
Below is the code -
ViewController *view= [self.storyboard instantiateViewControllerWithIdentifier:#"ViewController"];
view.anotherviewArray= [self.arrayFromAFNetworking objectAtIndex:indexPath.row];
If anyone can give me an idea, it would be helpful.
I believe it's bad practice to retain UI elements and pass them around your app. You should instead have some kind of a model containing your data, and pass this model from one view controller to the other. I'd recommend checking out tableview frameworks such as the free Sensible TableView framework, as they do an excellent job of providing such a model for you automatically.
I personally think that it's wrong approach to pass UI object as parameter to another controller.
As I would do it is create some object that encapsulates data model from this cell and pass this object to another view controller.
#interface DataObject : NSObject
#property id field1;
#end
UI part of cell can be easily copied in Interface Builder, so I don't see problem in that. Probably it would be great to have cell class that could fill necessary field from the object with data. This class you can use in both view controller that have to show the same cell
#interface CustomTableViewCell : UITableViewCell
- (void)customizeCellWithDataObject:(DataObject *)dataObject;
#end
I hope it makes sense to you
Assuming that you DO want to set the other data source with only this row, you need to pass it as an array.
view.anotherviewArray= [NSArray arrayWithObject:[self.arrayFromAFNetworking objectAtIndex:indexPath.row]];
But it's hard to tell from the little code that you have provided. I assume that since you are instantiating the viewController you are also transitioning to it below the provided code. If you are trying to set the array for a viewController already presented, you need to access that one, not create another, perhaps by having saved a reference to it an ivar within the current viewController or another accessible class.
I would also not name a ViewController view, it is confusing to anyone reading the code later on.
Editing for my comment below about traversing the hierarchy. Here is some code that I used in one iPad project to return the final presented viewController. This method is in the appDelegate. It is somewhat specific to my project, where there is only one navigationController. But you can adapt it to yours. You would test for a viewController that is of the class of your target view controller.
- (UIViewController *)topViewController {
UIViewController *rootViewController = self.window.rootViewController;
UIViewController *topViewController = rootViewController.presentedViewController;
while (topViewController && topViewController.presentedViewController) {
topViewController = topViewController.presentedViewController;
if ([topViewController isKindOfClass:[UINavigationController class]]) {
UIViewController *presentedViewController = [(UINavigationController *) topViewController topViewController];
if (presentedViewController) {
topViewController = presentedViewController;
}
}
}
return topViewController;
}
The other approach is to set a property to it when it is created and presented. We don't have enough code to get a good idea of your app as a whole. Where are you creating the ViewController instance that you are displaying? By that I mean where you are calling a segue to it, or pushing it onto a navigationController or call presentViewController. Wherever that is, you need to set a property or ivar to it. Let's say that you use a property in your appDelegate as a very generic case.
In your MyAppDelegate.h file you have
#property(nonatomic,strong) ViewController *viewController;
Wherever you first create it you set that property
MyAppDelegate *appDelegate = (MyAppDelegate*)[[UIApplication sharedApplication] delegate];
appDelegate.viewController = [self.storyboard instantiateViewControllerWithIdentifier:#"ViewController"];
I now think you are trying to add this to a mutableArray in the other ViewController. Then replacing your code from the tableViewCell above you would use
MyAppDelegate appDelegate = (MyAppDelegate)[[UIApplication sharedApplication] delegate];
[appDelegate.viewController.mutableDataArray addObject:self.arrayFromAFNetworking objectAtIndex:indexPath.row];
[appDelegate.viewController.tableView reloadData];
I will say that it is not great practice to use the appDelegate for the property. But it is a generic answer that would work. It's best to put it in a class which is common to the viewControllers that you are passing data between. Perhaps a single parent which holds these two viewControllers?

Resources