create every time a new uiviewcontroller or reuse the same? - ios

I want to know which is the better way: Either to create a new uiviewcontroller for instance, if in a view i have to load a search view to search new element on a web server, or load a view that display the result from a request, Which is the better way?
This:
MasterViewController.h
#property (nonatomic, strong) PreviewViewController *reviewViewController;
MasterViewController.m
-(void)openPreviewView
{
if (!self.previewViewController) {
self.previewViewController = [[PreviewViewController alloc] init];
}
[self.navigationController pushViewController:self.previewViewController animated:YES];
}
or this:
MasterViewController.m
-(void)openPreviewView
{
PreviewViewController *previewView = [[PreviewViewController alloc] init];
[self.navigationController pushViewController:previewView animated:YES];
}

I always create each view controller as it is needed. The only time I keep a view controller around is if, after doing some proper performance testing, I find that keeping a specific view controller around is better for the user and for performance. Of course you must deal with memory warnings to properly cleanup any cached view controllers.
Don't worry about performance optimizations too early. Wait until you find, through real testing, that you actually have an issue to worry about.

The first one. iOS devices are resource-constrained, so in the absence of a compelling reason not to, you should always re-use if you can.

Related

Am I properly using delegation? If not, how should I be doing this?

So I have taken the plunge into Objective-C programming for iOS development. I have a little app that I am working on, nothing special, but something to help teach me the ropes. The problem I am having is as follows:
Currently, I have two classes. The first being
ViewController
and the second being one that I created myself called
UserDecision
The View controller shows what is on screen, and UserDecisions currently takes the information from buttons pressed on screen, and performs the proper logic on it while working with my model class. My issue is, that I have an update UI method in UserDecision which needs to update the button properties (text, visibility, etc.) in ViewController if certain events take place. Because of this, I can't user an instance of ViewController because I won't be able to access the buttons on screen. So for this I created a delegate system:
#protocol updateUIDelegate <NSObject>
-(void)hideAll;
-(void)makeBackVisible;
-(void)updateOutput:(NSString *)output;
-(void)updateChoices:(NSString *)choices;
-(void)updateTrueButton:(NSString *)trueString;
-(void)updateFalseButton:(NSString *)falseString;
-(void)removeChoiceFromArray;
#end
The above protocol is defined in UserDecision.h, and then I assigned my ViewController as my delegate:
#interface ViewController : UIViewController <updateUIDelegate>;
And then I flush out said methods in my ViewController.m:
#pragma - updateUIDelegates -
//Called when the last screen is displayed
-(void)hideAll{
[_trueButton setHidden:true];
[_falseButton setHidden:true];
[_choicesText setHidden:true];
[_backButton setHidden:true];
[_resetButton setHidden:false];
}
//Makes back button visible
-(void)makeBackVisible{
[_backButton setHidden:false];
}
//Updates the text on the false button
-(void)updateFalseButton:(NSString *)falseString{
[_falseButton setTitle:falseString forState:UIControlStateNormal];
}
//Updates the text on the true button
-(void)updateTrueButton:(NSString *)trueString{
[_trueButton setTitle:trueString forState:UIControlStateNormal];
}
//Updates the output text box
-(void)updateOutput:(NSString *)output{
[_outputText setText:output];
}
//Updates the choices textbox
-(void)updateChoices:(NSString *)choices{
if(!choicesArray){
choicesArray = [[NSMutableArray alloc] initWithCapacity:4];
}
//If this is the first button press, add string to array and display
if([_choicesText.text isEqualToString:#""]){
[choicesArray addObject:choices];
_choicesText.text = [NSString stringWithFormat:#"%#", choices];
}
//Otherwise, add the new string to the array, and print the array
//using a comma as a way to concatinate the string and get rid of
//the ugly look of printing out an array.
else{
[choicesArray addObject:choices];
[_choicesText setText:[NSString stringWithFormat:#"%#",[choicesArray componentsJoinedByString:#", "]]];
}
}
//Removes the last choice from the array
-(void)removeChoiceFromArray{
[choicesArray removeLastObject];
[_choicesText setText:[NSString stringWithFormat:#"%#", [choicesArray componentsJoinedByString:#","]]];
}
This allows me to call theses methods by sending them as a message to self.delegate in my UserDecision class when needed.
This is my current setup. My issue has become that I want to create a modal seque view that pops up at the end (after a user presses a button to bring up the view), and which can be dismissed afterward. The problem I have is that this view, from the reading and research I have done online, can only be dismissed through delegation, unless I want things to get nasty. Now, I tried to implement this information in my class, but then I read that a class can only be a delegate to one other class. And since my ViewController(which is my main window) is already a delegate of my UserDecision class, I can't make it a delegate of the new View I have created, and thus can't dismiss the view. So, I am here to ask for your help. How can I go about solving this issue?
Also, for more of my code, should you want to have a look, here is a link to my gitHub: https://github.com/Aghassi/Xcode/tree/master/Bubble%20Tea%20Choice/Bubble%20Tea%20Choice
I read that a class can only be a delegate to one other class. And
since my ViewController(which is my main window) is already a delegate
of my UserDecision class, I can't make it a delegate of the new View I
have created
I don't believe that's true. You can make ViewController implement many different protocols, therefore being delegates to different classes/objects.
For example: (UITableViewDelegate and UITextViewDelegate can both be implemented on the same ViewController for 2 separate objects (UITextView and UITableView).
As for using delegation to close modal windows, another option is to use blocks as well.
It is possible for a viewController to dismiss itself. Just hook up a dismiss button to a function that calls something like:
[self.presentingViewController dismissViewControllerAnimated:YES];
or
[self.navigationController popViewControllerAnimated:YES];
Dismissal can be done with a delegate pattern but it is not required for everything.
You viewController class can be a delegate of multiple objects so it should be able to dismiss the modal view. The only issue is if its a delegate of multiple objects of the same class you may need to check which object is calling it.
Look at the tableView delegate methods as an example, the tableView calls them passing itself as the first parameter.
To dismiss a custom modal view you would define a different protocol anyway so there would be no problem with calling the same method.
See example below:
#protocol OSImageViewControllerProtocol <NSObject>
- (void)dismissImageViewer;
#end
#implementation OSImageViewController
- (void)loadView
{ //LOG(#"loadView called");
scrollView = [[ImageScrollView alloc] init];
scrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
UITapGestureRecognizer *doubleTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(scrollViewDoubleTapped:)];
doubleTapRecognizer.numberOfTapsRequired = 2;
doubleTapRecognizer.numberOfTouchesRequired = 1;
[scrollView addGestureRecognizer:doubleTapRecognizer];
self.view = scrollView;
}
- (BOOL)prefersStatusBarHidden {
return NO;
}
- (void)scrollViewDoubleTapped:(UITapGestureRecognizer*)recognizer {
//LOG(#"scrollViewDoubleTapped called");
[self.delegate dismissImageViewer];
}
#end
#implementation ViewController
-(void)browseImage:(UIImage*)image
{
OSImageViewController *_imageViewerController = [[OSImageViewController alloc] init];
UIImage *img = [[UIImage alloc] initWithData:UIImagePNGRepresentation(image)];
_imageViewerController.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
_imageViewerController.modalPresentationStyle = UIModalPresentationFullScreen;
_imageViewerController.delegate = self;
[self presentViewController:_imageViewerController animated:YES completion:^(void){
[_imageViewerController setImage:img];
}];
}
- (void)dismissImageViewer {
[self dismissViewControllerAnimated:YES completion:nil];
}
#end
I believe you want to display a modal view from your ViewController.
Let the the modal view be managed by say ViewController2. In ViewController2.h declare a protocol of ViewController2
#protocol viewController2Delegate
-(void)dismissViewController2;
#end
Now make ViewController implement this protocol
#interface ViewController : UIViewController <updateUIDelegate,viewController2Delegate>
Add the method to the ViewController.m
-(void)dismissViewController2
{
[self dismissViewControllerAnimated:YES completion:nil];
}
Now whenever you push a modal view(managed by ViewController2) from ViewController you set the delegate to self. Your ViewController.m code might look like this
ViewController2 *objViewController2 = [[ViewController2 alloc]init];
objViewController2.delegate = self;
[self presentViewController:objViewController2 animated:YES completion:nil];
Hope this solves your problem
COMMUNICATION PATTERNS
Delegation is one of the communication patterns that more or less loosely coupled objects use to communicate each other. iOS Framework provides the following patterns: KVO, Notification, Delegation, Block, Target-Action.
In general, there are cases where the choice comes down to a matter of taste. However, there are many cases that are pretty clear cut.
It's also important to note that the use of each of this patterns results in a certain level of coupling among objects involved into the communication process.
Let's focus now on Delegation, Block, Target-Action.
DELEGATION
Level of coupling (proportional to the level of mutual ignorance) : loose
It allows us to customize an object’s behaviour (decoration) and to be notified about certain events (callback). In this case, the coupling is pretty loose, because the sender only knows that its delegate conforms to a certain protocol.
Since a delegate protocol can define arbitrary methods, you can model the communication exactly to your needs. You can hand over payloads in the form of method arguments, and the delegate can even respond in terms of the delegate method’s return value. Delegation is a very flexible and straightforward way to establish some sort of blind communication between two object that should be loosely coupled for design reason. Let's think to the communication mechanism between a tableview and its dataSource delegate.
Conversely, if two objects are that tightly coupled to each other that one cannot function without the other, there’s no need to define a delegate protocol (use composition instead). In these cases, the objects can know of the other’s type and talk to each other directly. Two modern examples of this are UICollectionViewLayout and NSURLSessionConfiguration.
TARGET-ACTION
Level of coupling : very loose
Target-Action is the typical pattern used to send messages in response to user-interface events. Both UIControl on iOS and NSControl/NSCell on the Mac have support for this pattern. Target-Action establishes a very loose coupling between the sender and the recipient of the message. The recipient of the message doesn’t know about the sender, and even the sender doesn’t have to know up front what the recipient will be. In case the target is nil, the action will goes up the responder chain until it finds an object that responds to it. On iOS, each control can even be associated with multiple target-action pairs.
A limitation of target-action-based communication is that the messages sent cannot carry any custom payloads. On the Mac action methods always receive the sender as first argument. On iOS they optionally receive the sender and the event that triggered the action as arguments. But beyond that, there is no way to have a control send other objects with the action message.
BLOCK
Blocks are usually used to pass to an object a behaviour to be executed before its lifecycle end. Besides, they can also substitute delegates with a caveat relevant to the potential creation of retain cycle.
self.tableView.didSelectRowAtIndexPath = ^(NSIndexPath *indexPath) {
...
[self.tableView reloadData];
...
}
In this case the sender retain the table view whose selection block retain the sender so we'd better use delegation pattern.
An example in which block communication shines:
self.operationQueue = [[NSOperationQueue alloc] init]
Operation *operation = [[Operation alloc] init];
operation.completionBlock = ^{
[self finishedOperation]
}
[operationQueue addOperation:operation];
There's a retain cycle in the above code as well, but once the queue removes the operation, the retain cycle is broken.
Blocks are a very good fit if a message we call has to send back a one-off response that is specific to this method call, because then we can break potential retain cycles. Additionally, if it helps readability to have the code processing the message together with the message call, it’s hard to argue against the use of blocks. Along these lines, a very common use case of blocks are completion handlers, error handlers, and the like.
A CHART HELPING US TO MAKE THE RIGHT CHOICE
source: objc.io
In your specific case, I'd use the target-action communication pattern to dismiss the presented modal view controller.
For example,
ModalViewController *modalViewController = [[ModalViewController alloc] init];
[self presentViewController:modalViewController animated:YES completion:^{
[modalViewController.closeButton addTarget:self action:#selector(dismissModalViewControllerAnimated:)
forControlEvents:UIControlEventTouchUpInside];
}];

Array + UIView Controller + UITableViewController

I have a UIViewController class and a UITableViewController class. Within the UIViewController class I have an NSMutableArray.
I now have the issue of how to load data into my table view, a separate class, I must access the NSMutableArray I used to populate the previous UIViewController class.
I tried using a delegate to access the array in the UIViewControllerClass however the array had "0 objects" and was NULL
I would appreciate some guidance in the right direction here.
You could have one view controller hold a reference to the other view controller and query the public NSMutableArray on it for data. Aaron suggested this and it might be your best solution.
Or.. you have multiple view controllers trying to access the same set of data. Potentially you have other classes which will want to access this data also. You might want to consider pulling the data out of the view controller and storying it in a neutral location. You could store it in the AppDelegate and then reference the app delegates from any place you need it.
id<UIApplicationDelegate> appDelegate = [UIApplication sharedApplication].delegate;
NSMutableArray *myData = appDelegate.data;
You could also consider pulling all the logic of your data and the data itself into a separate class and use a Singleton It would allow you to access/manipulate the data fairly easy from anywhere.
The last 2 methods would insulate data from user interface controller objects and prevent the need from potentially unrelated objects needing to hold references to one another. Used properly it will reduce code complexity and mage future changes easier to manage.
Create an NSMutableArray property on your UITableViewController class like so:
#interface CustomTableViewController : UITableViewController
#property (strong, nonatomic) NSMutableArray *dataFromOtherClass;
#end
And then when you transition, perhaps like this, you can set the dataFromOtherClass property:
CustomTableViewController *controller = [[CustomTableViewController alloc] initWithNibName:#"CustomTableViewController" bundle:nil];
controller.dataFromOtherClass = myNSMutableArrayData; // <-- Set data like this
[self.navigationController controller animated:YES];
// Or ...
[self presentViewController:controller animated:YES];
// Etc...

Generate new UITableViewController and show it

I'm working with tableviews showing a hierarchy data structure. I take the first node and show the sons in the tableview and repeat it until the end of the tree. I'm doing it in this way:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if([[actualNode getSonAtIndex:0] sonsCount]>0) {
NSLog(#"New level");
actualNode = [actualNode getSonAtIndex:indexPath.row];
[self.tableView reloadData];
} else {
NSLog(#"Service");
[self performSegueWithIdentifier:#"Service" sender:NULL];
}
It is not the correct way but the problem is that I don't know how many levels has the tree so I can't create them in the Storyboard. The idea is create only one TableView for showing each level but doing it in that way I can't go back to the previous level on the NavBar like I could do if I was working with some controllers in the storyboard and I don't have animations. So, is there any way to do it? something like:
MyNextLevel *nextlevel = [MyNextLevel alloc];
[nextlevel setNode: actualNode]
Myactualtableview = nextlevel; (Here the next level is showed in the screen with animations and with the possibility to go back to the previous level)
Thanks.
This is why I don't like storyboards. It has always been possible to do what you want to do (if I'm understanding it correctly), but storyboards detract from it; they don't have the flexibility of doing things in code, which is what we always had to do back in iOS 3 and iOS 4 anyway.
Anyhow, you want to do something like this:
UITableViewController* tvc = [[MyTableViewController alloc] initWithNibName:#"MyNib" bundle:nil];
[self.navigationController pushViewController:tvc animated:YES];
Your business logic can just pick the class for MyTableViewController. Alternatively, MyTableViewController could be something flexible, where between those two lines you give it some configuration info that causes it to show the right data! (I have to admit, though, that you could do that same thing using a storyboard, configuring the table view controller in performSegue:.)

Why do AppDelegates have an instance variable for the root controller?

This example is taken from The Big Nerd Range iPhone book (page 143/144) - ItemsViewController is a subclass of UITableViewController:
#interface HomepwnerAppDelegate : NSObject <UIApplicationDelegate>
{
UIWindow *window;
ITemsViewController* itemsViewController;
}
....
itemsViewController = [[ItemsViewController alloc] init];
[window setRootViewController: itemsViewController]
My question is why is it necessary to have the iVar itemsViewController, why not just do this instead:
...
window.rootViewController = [[ItemsViewController alloc] init];
I presume the window will destroy its rootViewController when the app exits and thus there's no leaks, and the window will be in existence for the lifetime of the app so I don't understand why this and many other example have a separate iVar for the root controller?
TIA
The biggest advantage is simply that you can access the methods of your view controller without needing to cast over and over again:
[itemsViewController doSomething];
// vs.
[(ItemsViewController *)window.rootViewController doSomething];
Depending on the app you might need to refer to the root view controller frequently from the app delegate, for example when implementing the handlers for entering background/foreground and similar app delegate callbacks.
There is absolutely no need to keep the ivar around if you don't need it.
BTW, you will leak ItemsViewController if you don't autorelease it. (Unless you are using ARC)
The reason is historical, I think. Back when that book was written, the window and root view controller both used to be IBOutlets and were set from a nib file called MainWindow.nib.
Also, UIWindow didn't used to have a rootViewController property to assign the control to (the root view controller.view was just added directly as a subview to window), so if you didn't store it in an ivar then it wouldn't be retained by anything and your app wouldn't work because the root view controller would be released as soon as it was created.
These days however, since iOS4 and now ARC, the base project template has been updated and doesn't even have ivars any more (which are no longer needed). It does still have an #property for the view controller, but it's technically not needed any more either, and your alternative solution of assigning a new controller directly to the window.rootViewCOntroller would work fine.
This is totally stylistic choice. There are other ways to get the convenience accessor. I don't ever create an ivar in my rootViewController never changes. I will usually go for a read only property.
#property (nonatomic, readonly) MyRootViewController *rootViewController;
- (MyRootViewController *)rootViewController {
if ([self.window.rootViewController isKindOfClass:[MyRootViewController class]) {
return (MyRootViewController *)self.window.rootViewController;
}
return nil;
}

Help using a view controllers methods before the view is loaded

I have a UIViewController called DebugViewController that contains a UITextView, and a public method called debugPrint which is used to write an NSString into the UITextView and display it.
Is it possible to write into the UITextView before I open the UIViewController, so that when I open it, the text previously written into it is displayed?
In my parent view controllers viewDidLoad method, I'm calling initWithNibName on the DebugViewController as follows
debugViewController = [[DebugViewController alloc] initWithNibName:#"DebugView" bundle:nil];
I then call debugPrint as follows
[debugViewController debugPrint:#"viewDidLoad"];
And some time later I call the following to open the debugViewController
debugViewController.delegate = self;
debugViewController.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[self presentModalViewController:debugViewController animated:YES];
However all the text previously written is missing.
Please let me know how I can use a view controllers methods before the view controller displayed to the user.
Thanks,
JustinP
What you are doing is a little non-standard. The danger with that as always is that if you don't really have an expert grasp on what you're doing, you can quickly find yourself in difficulty.
If you want something set before the view is displayed to the user, then the best way to do that is to do it in the viewWillAppear method. Put it there rather than in viewDidLoad because a view might loaded once but appear many times. Where you place it depends on whether the data changes from appearance to appearance.
So, if your data is pretty static and won't change, use the viewDidLoad method.
Assuming that you'll go for the viewWillAppear option, let's do the first step by having an ivar in the view controller:
NSString *myText;
set that after init:
debugViewController = [[DebugViewController alloc] initWithNibName:#"DebugView" bundle:nil];
debugViewController.myText = #"My text here";
then, in debugViewController's viewWillAppear method:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
myTextView.text = myText;
}
The view controller life cycle is complex as you can see from the View Controller Programming Guide for iOS. So I'd say best not stray from the path of least resistance unless you have good reason. That said sometimes the best way to learn is by experimentation.

Resources