I recently asked a question regarding my incorrect use of instance variables on UIViewControllers. The answers I got brought to light another fundamental issue: I think I might be using UIViewControllers all wrong as well.
My typical storyboard looks something like this:
As you can see, each view controller has a custom class. One of those custom classes might look like this:
// SecondViewController.h
#import <UIKit/UIKit.h>
#import "MasterViewController.h"
#interface SecondViewController : MasterViewController
#property (strong, nonatomic) NSData *incomingData;
#end
// SecondViewController.m
#import "SecondViewController.h"
#import "ThirdViewController.h"
#interface SecondViewController ()
#end
#implementation SecondViewController
- (void)viewDidLoad {
[super viewDidLoad];
// do stuff with data, then pass it on to next view controller.
}
#end
Each view controller is a subclass of MasterViewController. In MasterViewController, I'll usually put global things like user state validation, for example. That way every view controller in the app can validate user state without reimplementing it each time.
About that incomingData property...I wasn't sure how else to pass data between UIViewControllers without using a property on a custom class...hence three separate custom classes, each one not doing all that much.
So here's where it gets murky for me...
Should I be keeping important methods and instance variables in a "master" view controller so all other view controllers can subclass it and have access to those methods and variables? Or is that ridiculous?
Am I causing more work for myself by not using proper class extensions or categories? Would those help me keep things a little more streamlined and organized?
My current system works for the most part, but it can get unwieldy when I have lots and lots of view controllers, each with its own custom class.
Obviously, the first step which you may have done, and whether you've completely grasped or not, is to really understand the Model-View-Controller pattern. A pretty good tutorial I did with a quick google search is here: http://codewithchris.com/how-to-make-iphone-apps-mvc-one-pattern-to-rule-them-all/.
The next thing I'd suggest is that your controllers don't need to be subclasses of anything but UIViewController. There's a difference between the view hierarchy and the class hierarchy. In your storyboard, you're outlining a view hierarchy - which view/controller leads to the next view/controller. They can all be independent. They can still pass data even without being subclasses of each other, or of a previous controller.
You can still pass data directly through properties. Why not be more specific about what data needs to be sent? Instead of sending a generic NSData blob, specify what you need one controller to tell the next. For example, if you're passing in a username from one to the next, use:
#property (nonatomic, strong) NSString *username;
for the second controller, and in your first controller, set it:
self.secondController.username = #"my name";
If from the second controller to the third controller, you don't need the username, don't make it a property of the third controller. Maybe the third controller only displays a number:
#property (nonatomic, strong) NSNumber *someNumber;
From the second controller, just set its value:
self.thirdController.someNumber = #5;
Now, I like to use some global variables and methods. Those are sometimes useful when you have helper classes that don't need to be instantiated. One way to do this is to just have a class variable:
FirstController.h:
+(NSString *)defaultName;
FirstController.m
+(NSString *)defaultName {
return #"Default";
}
Then in second controller, you can do something like
self.username = [FirstController defaultName]
There are other patterns, like delegate patterns, where the second controller asks its delegate (the first controller) what the username should be:
self.username = [self.delegate username];
But for this you have to set up a protocol. Ray Wenderlich usually has very good tutorials: http://www.raywenderlich.com/46988/ios-design-patterns
What you have here is Strong coupling, which is considered to be a bad thing.
Lets say you want to create a new application, and you want to copy a view controller from the old application, at the moment it would be very difficult for you as you would have to modify a lot of things.
The goal is to achieve weak coupling, which means that every class in your project can work independently from one another.
You can store your data as a property on your App Delegate, which is a singleton, so you can get an reference from an any class using AppDelegate *appDelegate=[UIApplication sharedApplication].delegate;. Or you can define a new singleton object to store the data.
The pattern you are using is dependency injection, which many people prefer to the singleton pattern.
If all of your view controllers are inheriting from the same superclass (MasterViewController) and all have that same property then you can simply define the property in the superclass.
Related
I'm rather new to programming and wondering about a certain (best) practice:
Let's assume we have an app with several view controllers. In our case, most of those need the functionality of alerting the user about certain circumstances, make use of an activity indicator or depend on other similar general functionality. So far I've learnt how to implement such methods but then just copied the whole bunch of code to each view controller when needed. Doing so, every view controller gets filled up with a lot of extra code. I know it's possible to make code kind of "global" by moving it to the top of a view controller, outside the class brackets. But as we need to ensure that certain subviews are added to the right view controllers when calling those methods I'm not sure yet what the best way to go - in general - would be.
Is there a commonly used practice that differs from my approach when defining such - let's say - alert behaviors (defining variable/constant and its needed methods) which are used in multiple view controllers?
Objective-C provides two general ways of reusing code:
Inheriting a base class, and
Using a shared function.
The first case is straightforward: if you need a specific functionality in several view controllers, make a base view controller with the shared methods, and then derive your other view controllers from it:
#interface BaseViewController : UIViewController
-(void)sharedMethodOne;
-(void)sharedMethodTwo;
#end
#interface FirstViewController : BaseViewController
...
#end
#interface SecondViewController : BaseViewController
...
#end
#interface ThirdViewController : BaseViewController
...
#end
The second case can be implemented either as a helper class with class methods (i.e. with + instead of -) or with free-standing C functions.
You should look to see what could be moved from the controller level to the view level. It sounds like you're reusing a bunch of views so I would create subclasses of UIViews that can implement themselves. Then in the view controller you can handle when the views should appear and the data associated with them but you don't need to rewrite how to implement the views.
I have two controllers in the storyboard, embedded in a NavigationController, and there is a segue to switch between these.
Passing data from the first controller to the second one is pretty straightforward by implementing prepareForSegue, and set the properties of the second controller using segue.destinationViewController.
I should pass back data to the from the second controller to the previous one also. I googled, but I have not found any simple, but working code to demonstrate it.
Would you be so kind give me a simple sample about the best way to do it?
Thanks in advance!
In your second view controller class you create a protocol and delegate. The first view controller will set it self as the delegate in prepareForSegue and implement the protocol methods. The second view controller will then call the methods to pass data back to the first view controller. Here is some code from one of my projects as an example.
#protocol TableSelectorDelegate <NSObject>
#optional
- (void)didMakeSelection:(id)selectionString forType:(NSString *)dataTitle;
- (void)didAddNewValue:(NSString *)newValue forType:(NSString *)dataTitle;
#end
#interface TableSelectorViewController : UITableViewController
#property (nonatomic, weak) id<TableSelectorDelegate> delegate;
#end
when you set the data you're passing to the second controller you can also set a pointer to the previous one.
The "recommended" way of doing this is using a delegate. Have the first view controller set itself as the delegate of the new view controller during the -prepareForSegue: call, then when you're done, you call whatever delegate methods you've defined.
This is a bit more work than tightly coupling the two controllers, but it actually saves time if you ever find you need to use the controller in a slightly different way. If you watch the WWDC'11 video on using IB and Storyboards, they actually go through this pattern in depth and include code examples and demos, so I recommend taking a look at that.
I've been studying all of the variants to this question of how to pass data from one view controller to another and have come to see that Apple's Second iOS App Tutorial has not only the code but a lovely explanation of everything involved.
I am working on an iPad app and i utilise a UISplitview for my program.
Now on the main detail view of my program i have a uiscrollview on which i add two labels.
UIScrollView *scroll=[[UIScrollView alloc]initWithFrame:CGRectMake(0,0, self.view.frame.size.width,self.view.frame.size.height)];
scroll.contentSize=CGSizeMake(320, 1600);
scroll.showsHorizontalScrollIndicator=YES;
scroll.backgroundColor=[UIColor clearColor];
[self.view addSubview:scroll];
This is the code i create on the first main page. Now imagine we push the second view, from that second view i can access everything by saying
[self.detailViewController.view addSubview:detailViewController.Image];
but when i try to add labels to the subview saying
[self.detailViewController.view.scoll...
but i cannot find the scroll object, BUT the background of the scroll set in the first view comes over in the second view. AND i cannot change the background of the first view.
I decided to make a second scrollview untop of the first (which works) but i much rather know how i can access the first view i created throughout the whole program since it would negate me having to waste space creating scroll views. but if i have to create all the views i need i want to be able to somehow delete or release them so the picture from scrollview one doesn't get transferred all the way too scrollview 3
Thank You
You have to create properties for all the variables that you want to access from other classes . So in your case
DetailsViewController.h
#interface DetailsViewController : UIViewController {
// class member variables here that can be accessed
// anywhere in your class (only in your class)
}
#property(nonatomic, strong)
//retain instead of strong if you are not using ARC or other types (weak, copy, readonly)
SomeClassThatYouHave *propertyThatCanBeAccessed
//declare here public instance methods
#end
In your DetailsViewController.m you will have:
#interface DetailsViewController (Private)
//declare private methods here or private properties
#end
#implementation DetailsViewController
#synthesize propertyThatCanBeAccessed;
//methods implementation here
#end
Now you can access the property of your DetailsViewController like detailsViewControllerInstance.propertyThatCanBeAccessed but you must alloc/init the instance.
Hope that this will give you an idea of class structure in the future.
I'm wondering if the way my CoreData manager interacts with my views is the correct/best/most efficient way possible.
I use a standard singleton pattern, the Apple example, to house my CoreData stack. It also contains utilities for use with a web service and other helper methods.
There's a UINavigation controller with 2 viewControllers, a tableViewController and another viewController programmatically created viewController.
The first two viewControllers are a setup (web service address etc) and login respectively. Right off the bat I pop to the login view (second view). On a successful login, the tableView is pushed. On the selection of a cell, a view is created programmatically and pushed.
Each of these views have a need for the data store, so in their viewDidLoad, I'm grabbing the singleton.
Is this the way to do things? What are other ideas? Can the navigation controller expose properties to all of it's embedded views?
Thanks.
Until recently I used Matt Gallagher's synthesize singleton to do the same thing you mention.
However, recently I converted my project to use ARC, so the singleton setup simplified somewhat. Now I have:
static MMPersistentStoreController *sharedMMPersistentStoreController = nil;
+ (MMPersistentStoreController *)sharedMMPersistentStoreController {
if (sharedMMPersistentStoreController == nil)
{
sharedMMPersistentStoreController = [[self alloc] init];
}
return sharedMMPersistentStoreController;
}
I think this a pretty standard approach. Certainly it's better than passing the object around everywhere.
As for alternatives, I guess you could synthesize it as a property of your application delegate then access it via UIApplication.
With the former approach, it's a static instance created by the MMPersistentStoreController class object. With the latter, the instance is is an ivar of an object that will only ever exist once in your application.
I'm designing an iPad app where little UIScrollViews have their own UIViewController . Those controllers have in their view a button that call an IBAction method. But it isnt working, in fact, it doesnt seem that they are being pressed in the simulator.
Here is some code to give you an idea of what im am doing .
// In UIViewController A (say the parent or root that have several UIScrollViews)
MiniViewController * mini = [[MiniViewController alloc]init];
[scrollView1 addSubview:mini.view];
//repeat the same process a couple of times with different uiscrollsviews and instances of miniviewcontrollers
Now The MiniController is very simple as you can guess, i only post the .h file
#interface MiniControlador : UIViewController {
IBOutlet UIButton * button;
}
#property (nonatomic, retain) IBOutlet UIButton * button;
- (IBAction)doSomething:(id)sender;
#end
You can see that i used Interface builder to connect an UIButton "button" to a method called doSomething. But as i already said, it isnt working.
One more thing. I also tried to add a button to the UIScrollView with the Mini Controller instance programmatically.And it worked! But I certainly believe that it's extremely hardcoded.
What do you think? I'll appreciate any suggestion(s).
Apple's View Controller Programming Guide is an important read, and explains a lot about Apple's philosophy of one-view-controller-per-screen.
Much of the behavior of view controllers is built on the assumption that there is only one view controller operating at a time. When that assumption is violated, the behavior is undefined (or at least undocumented). In this case, your description suggests that the normal view controller behavior of inserting the controller into the responder chain between its root view and that root's superview (usually the previous screen) isn't working.
While you may find methods of initialization that do work properly, they're not going to be guaranteed to work, and the behavior is liable to change with future OS updates.
Edit: A relevant quotes from the View Controller Programming Guide:
Each custom view controller object you create is responsible for managing all of the views in a single view hierarchy. In iPhone applications, the views in a view hierarchy traditionally cover the entire screen, but in iPad applications they may cover only a portion of the screen. The one-to-one correspondence between a view controller and the views in its view hierarchy is the key design consideration. You should not use multiple custom view controllers to manage different portions of the same view hierarchy. Similarly, you should not use a single custom view controller object to manage multiple screens worth of content.
Thanks guys, I finally solve this using objects of a class (that I called GenericViewController). It actually acts like a regular UIViewController, the IBActions responds well to any event (i.e. buttons being pressed).
I used an IBOutlet UIView in order to contain UILabels, buttons...and so on.
Here is some code if anyone is interested.
#interface GenericViewController : NSObject {
/* Some IBOutlets here*/
//like a regular UIView of an UIViewController, this holds the rest of the outlets
IBOutlet UIView * view;
}
//some IBActions here
}
Then the UIScrollView only add the view of each GenericViewController object
[scrollView addSubview:genericViewControllerObject.view];
If anyone has a better solution, please let me know :)
Are you sure you are loading the view from the xib you made in InterfaceBuilder?
I'm doing something similar in my app, and it's working for me.
I'm implementing the init method like this:
- (id)init
{
if (self = [super initWithNibName:#"__your_xib_name__" bundle:[NSBundle mainBundle]])
{
// TODO: Add additional initializing here
// ...
}
return self;
}
If you are not loading the view from the xib, then there will be no connections made (no IBOutlets initialized and no IBActions triggered).