I've extracted my NSFetchedResultsController's into a separate object. I'd like to monitor when the view controller appears and disappears so that I can pause and resume the FRC delegate methods to update the tableview with new content. Is this possible without any responsibility from the view controller itself? I.e. I know I could use delegates or notifications, but I am looking for a solution where I don't have to sprinkle code all over the view controllers.
It seems there isn't an official way to do this, so here's what I did.
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self.fetchedController willAppear];
}
And then handled the necessary logic in there... pretty basic.
Maybe another time I'll post about my fetchedController. It's pretty neat: it holds a UISearchController (and delegates), 2 data sources (one for the regular view, and one for the search). There's a protocol that the view controller implements (tableView, entity name, context, sort descriptors, configureCell, etc) so I never have to create search controllers, NSFetchedResultsController's, or any of the delegates directly. It's much cleaner than having a god UIViewController superclass.
Related
Right now I have a view controller that handles a lot of network requests. They are each a subclass of a NetworkRequest class and this view controller is the delegate of all of them. It implements one callback function, networkRequestDidFinish.
The problem is that all these network requests are separate objects, and they will all call that same function. What is the proper way to design this? Right now I go through a bunch of if statements in networkRequestDidFinish to see what kind of network request returned. It feels wrong though, but I am not sure what is conventional to do in this case.
Thanks.
One useful pattern here is to be sure that the delegate methods pass self to the view controller. It sounds like you might already be doing this - if you're using a series of if statements, you probably have a pointer to the relevant NetworkRequest. If you aren't, or are not sure, read on.
You see this pattern pretty much wherever delegation is used. As an arbitrary example, take the UITableViewDelegate protocol. The first argument of each of the delegate methods is a UITableView. For example:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
When a UITableView instance calls this delegate method, it passes self as that first argument. It does something like:
[self.delegate tableView:self heightForRowAtIndexPath:0];
Then, the delegate knows which UITableView it's dealing with, because it has a pointer dropped in its lap, as the argument tableView.
In your case, I would start by adding a parameter to the delegate method networkRequestDidFinish, changing its signature to:
- (void)networkRequestDidFinish:(NetworkRequest *)networkRequest
That way you can tell which instance of NetworkRequest has called the delegate method.
Already had that, or that's not good enough? Well, the next thing I'd say would be to consider whether you really need to perform different actions based on the actual class of the NetworkRequest instance that's calling the delegate method. If you're just passing along the data, the answer is probably no. For example:
- (void)networkRequestDidFinish:(NetworkRequest *)networkRequest {
[self processData:networkRequest.data];
}
That method doesn't care what class networkRequest really is. But you seem to care, since you're doing "a bunch of if statements." Then I would say that it might be a mistake to have them all hitting one delegate method. Instead, you might want to get rid of a delegate on NetworkRequest, and instead add a protocol to each of the subclasses of that class, specific to the subclass.
What?
Let's look at an example.
Imagine that one of the subclasses of NetworkRequest is FooNetworkRequest which, of course, requests foos. Its header might look like this:
// stuff...
#protocol FooNetworkRequestDelegate
- (void)fooNetworkRequestDidFinish:(FooNetworkRequest *)fooNetworkRequest;
#end
#interface FooNetworkRequest : NetworkRequest
#property (weak, nonatomic) id<FooNetworkRequestDelegate> delegate;
// stuff...
#end
You apply a similar treatment to all the other subclasses of NetworkRequest. Then, your view controller would adopt each of these protocols, and have a separate method for each subclass of NetworkRequest.
That still seems kind of dirty, right? It does to me. Maybe this is a hint that your view controller is trying to handle too many things at once. You should consider trying to spread out the responsibility for all these NetworkRequest subclasses to multiple view controller or model classes.
If that's not an option, you can at least make your view controller's source a little easier to read by using one or more categories. Put your view controller's main behavior in its .m file, as usual, and then create a category on that view controller that adopts the proper protocol(s) and handles the requests.
There are generally 2 nice procedures.
You can use block instead of the delegate. That means you can send a block to your request class either when instancing it or when you make the request.
Use a target/selector pair system to make it look kind of like adding a target to an UIButton. NSInvocation should do the trick.
In my app, some studies I've done show that when the user views an article, the vast majority of the time (85%+) they load the accompanying comments view controller that goes along with the article.
I'd love to load this view controller while they're reading the article, so when they tap the comments button to transition to the comments view controller the view controller is ready without any loading times.
How would I go about accomplishing something like this? At the moment when the user taps the button I call performSegueWithIdentifier: and pass it the identifier I set in the Storyboard.
And obviously, for the cases where the user decides to go back to the root view controller (say, a list of articles) I'd want to cancel the loading of that comments view controller as it would be wasteful to continue at that point.
If you keep the data model separate from the UI, you shouldn't have any trouble creating the views on the fly, and almost nothing to gain from creating them earlier.
A reasonably standard approach is to bring the view in with blank or filler data, and have the data call go out to the middle tier, with asynchronous handlers processing callbacks.
This is much easier with the block based completion handlers available in the iOS 7 flavored NSURLSession, and only slightly harder with NSURLConnection (which listens for responses on the main thread, but can be thrown into the background once you catch the response).
So my advice would be to focus on backgrounding the data calls and responses, and strongly differentiate between displaying UI and populating the UI with data. If your data manager is separate from your View Controller, nothing is stopping you from "pre-fetching" the data a little early, and then potentially having it ready when the ViewController needs it. It's a perfectly normal load balancing / customer experience technique for high value data.
The solution I'm about to describe is a bit of a hack - it doesn't really conform to the proper model-view-controller design pattern that Ryan mentioned. That being said, it might give you an idea about how to proceed. Perhaps you can improve on it to make it cleaner.
First, define a #protocol in the App Delegate. Let's call this protocol CommentQueryDelegate; it should define a method called -(void)handleCommentQuery. Also give your App Delegate a strong property to store the comment data and a weak property to store a delegate object, like so:
#property (nonatomic, strong) NSMutableArray* arrayOfComments;
#property (nonatomic, weak) id<CommentQueryDelegate> commentQueryDelegateObject;
Make sure to initialize the both of these properties to nil.
Somewhere in the article view controller, use dispatch_async() to asynchronously query your database and retrieve the comments while the user is reading the article:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^
{
AppDelegate *appDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
// query your database here
appDelegate.arrayOfComments = [NSMutableArray array];
[appDelegate.arrayOfComments addObject:#"someComment"];
[appDelegate.commentQueryDelegateObject handleCommentQuery];
}
If the query is completed before the user segues into the comments view, the handleCommentQuery message will be sent to the nil object, which will have no effect.
Now, in the comments view controller's viewDidLoad method, set the commentQueryDelegateObject property of the App Delegate to self. You will need to specify that the comments view controller conforms to the comment query protocol. Next, check to see if the App Delegate's arrayOfComments property is nil. If it isn't, great - display the comments immediately. Otherwise, display a UIActivityIndicatorView.
Implement the -(void)handleCommentQuery method in your comments view controller. This method should disable the activity indicator and display the comments.
One final thing to consider - the strong pointer to the arrayOfComments object will keep it from being deallocated, so you should set this pointer to nil once you're done with your article view controller.
I have a viewController, in which I have a view in which I draw like in Paint by tapping. In that controller I have a method to clear screen -
- (void) clearS
{
[(TouchTrackerView *)self.view clear];
}
Now, as I don't want to occupy my screen with buttons, I have button in another screen that resets application to starting position, and I want it to clear screen. That button belongs to different view controller, and among other things I want it to call my drawing view controller and ask it to wipe screen clear. So I have setup notifications, like this in draw view contoller:
[[NSNotificationCenter defaultCenter] addObserver: self selector: #selector(clearS) name:#"clearScreen" object: nil];
And like this in my view controller from where I click a button:
[[NSNotificationCenter defaultCenter] postNotificationName:#"clearScreen" object: nil];
As I understand, when the button gets clicked, notification gets posted, and method is called. And it crashes.
[(TouchTrackerView *)self.view clear]
As I understand the problem is that at the moment of method invocation the "self" variable is my non-drawing view controller, and it tries to perform clear method on view of itself, which it lacks and crashes.
How do I send the method to correct view controller?
Is there a flaw in my thinking? Maybe this can be approached in a better way?
EDIT:
I have found the problem,
[(TouchTrackerView *)self.view clear]
calls view and it is UIScrollView, and it does not support clear method. So I have made a property containing the correct view, and changed it to
[(TouchTrackerView *)self.correctView clear]
and it works like a charm.
I have chosen notification because it is only two lines of code, and I am beginner and it is hard for me to wrap my head around delegates, so I will leave this as it is, especially that it works.
At the philosophical level, while I'm sympathetic to the observations from others that you could replace the notification with a delegate pattern (or, better, delegate protocol pattern), it strikes me that this is not an appropriate example of having two controllers communicating directly to each other at all. You probably should be employing a MVC model, where the editing view controller, A, is designed for the editing of a drawing (and thus updates the model and coordinates the view) and the reset view controller, B, should just update/reset the model. If A needs to be informed of model changes, then apply a delegate-protocol pattern there, between the model and controller A. But I don't think B should be communicating with A at all.
At a practical level, there's absolutely no reason why notifications shouldn't work just fine. Unfortunately, you haven't shared enough for us to answer the question. You should share the specifics of the error message and we might be able to help you more.
I think that notifications, which are in nature a one-to-many method of communication are not good to use here. A better approach would be to hook one as the delegate of the other -> once the button is pressed and the corresponding IBAction is invoked, the delegate (in your case, the view controller you use for drawing) should get a message and perform whatever it is it needs to do.
I would avoid notifications unless you are trying to broadcast out some info that multiple objects may be interested in.
A better approach might be to create a delegate protocol for the painting view controller --
So the PaintingViewControllerDelegate Protocol may have methods like so
(void) paintingViewControllerWillClear:(PaintingViewController*)paintingViewController;
(void) paintingViewControllerDidClear:(PaintingViewController*)paintingViewController;
Now the controller with the buttons becomes the delegate to the PaintingViewController and that object provides the methods of the PaintingViewControllerDelegate protocol as needed.
I am currently developing an iPad application. For business reasons there wont be any data persistence on the device. The data will be accessed from a back-end server as needed using NSURLConnection. I have developed a 'model' object which does all the network access. The UI has a split view controller with a table view controller inside a UINavigationControlller as the root controller. User will drill-down on the table view controller to eventually load the detail view controller. The table Viewcontrollers are passing a reference to the model object when they are being loaded into the UINavigationController so that they can dynamically generate parts of the Table View Cell from the model. In order to be responsive, each Table View controller sets itself as the delegate of the Model object in the view will appear and when the cell is selected, queries the model object, which in turn updates the UI via a delegate method.
My question is where is the best place to set and unset the delegate of the data model?. Currently I am setting the delegate in the ViewWillAppear and setting it to nil immediately after navigation Controller:pushViewController:Animated.
// Setting the delegate
- (void)viewWillAppear:(BOOL)animated {
// set ourself as the delegate
[[self dataModel] setDelegate:self];
// Get the count of studies
[[self dataModel]GetListOfDiagnosticStudyResultsForID:[[self currentPatient]patientID]];
}
// setting delegate to nil
DiagnosticStudiesViewController *selectedVC = [[DiagnosticStudiesViewController alloc] init];
selectedVC.dataModel = self.dataModel;
[[self dataModel]setDelegate:nil];
[[self navigationController]pushViewController:selectedVC animated:YES];
Is this appropriate? Could you think of any issues with this pattern. The program is very responsive and I do not see any issues in the instruments. Is there a better way to do this?.
Sorry that this question is long winded.
I think this is an okay approach but there are a couple of considerations to be made:
You're sharing the dataModel with 2 views so you may have to update the view when you return to the DiagnosticsStudiesViewController's parent (self in your code) depending on how dataModel data is displayed.
This might get hairy in the future if you need to thread your code. In that case you might have to make a copy of the dataModel to pass to DiagnosticsStudiesViewController or handle edits to dataModel in a thread-safe manner.
You'll obviously require a network connection for both view controllers to work so you've made a workflow decision with your two view controllers by pulling dataModel from the server. In the future it may be hard to uncouple these view controllers.
If it works for your case and the decision has been made to not persist I think you'll be fine.
I would like to know how to best possible address the following issue:
I have a single ViewController. Its view contains a great number of complex subviews (subclass of UIView). Due to the complexity some of these UIViews initialise their own UIGestureRecognisers and implement the according target actions. As I want to coordinate the gestures of various subviews I have to set the single once ViewController as the gesture's delegate.
There are various possibilities for that.
1) Initialize ALL gestures in the the viewController (this will lead to a massive viewController)
2) defining a protocol in the UIVIews (getViewController), implemented by the ViewController
#protocol CustomViewDelegate <NSObject>
#required
- (UIViewController *)getViewController;
#end
3) customise the init method of the UIViews and using the ViewController as an option.
- (id)initWithFrame:(CGRect)frame andViewController:(UIViewController *)vc;
What is the most elegant possibility to solve this issue? Is it OK to implement target actions inside a UIView object?
Thanks for your thoughts...
If you're defining custom UIView subclasses, you can invest them with as much logic as it makes sense to store local to them, give them delegate protocols to pass anything else up and, as long as you expose the delegate as an IBOutlet, you can wire up your view controller as the relevant delegate directly in Interface Builder or the UI designer part of Xcode 4. I personally think that would be the most natural way forward, since it consolidates any view-specific logic directly in the view and lets you do the wiring up where you would normally do the wiring up.
In terms of overall design, such a scheme conforms to model-view-controller provided your views are doing only view-related logic. So, for example, if you had a custom rectangular view that can take a swipe anywhere on it to reposition a pin, and the 2d position of the pin affects some other system setting, you'd be correct to catch the gesture in the view, reposition the pin and then send updates on its position down to the delegate, which would fulfil the role of controller and push the value to any other views that are affected and out to the model.
Commenting on your suggested solutions directly:
(1) this would focus all logic into the one controller; whether it's correct from a design point-of-view depends on the extent to which you're having to interrogate your custom views (in that you don't want to end up treating them as mostly data that external actors have to know how to manipulate) and the extent to which you want to reuse logic.
(2) I'm not sure I entirely understand the suggestion — what is getViewController defined on and how does it know how to respond? If it's the UIViews themselves and the view controller has to identify itself first then I'd suggest just adopting the delegate pattern wholesale rather than specialising to views and view controllers, e.g. as you may want to build compound views that tie together logic from several subviews.
(3) as a rule of thumb, the sort of things to pass to init are those that the class actually needs to know to be able to initialise; it would probably be better to use a normal property to set the controller after the fact. Or make it an IBOutlet and wire it up so that it happens automatically via the NIB.