I have an app with a simple "drill in" pattern. I present a list of items in either map mode or list mode. Clicking on one dives into a more detailed "edit" view controller. My edit controller has
#property (assign, nonatomic) BOOL showMap;
And in source implementation setter/getter methods:
#pragma mark - Properties
- (BOOL) showMap {
return self.viewModeSegments.selectedSegmentIndex == 1;
}
- (void) setShowMap: (BOOL) showMap {
self.view; // had to add this, it's a hack
self.viewModeSegments.selectedSegmentIndex = showMap ? 1 : 0;
}
I set this property in the initiating controller with:
#pragma mark - Navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString: #"ValveEditSegue"]) {
ValveEditController* controller = segue.destinationViewController;
controller.valve = self.selections.anyObject;
controller.showMap = ShowMap;
}
}
What I discovered, is that the showMap setter happens before the view has been populated, so the viewModeSegments was still nil. I experimented with the hack shown in the setter, that forces the access to self.view to make sure it's loaded. But that seems like a bad idea. What I don't know, is what pattern I should use instead.
I could make the showMap a normal property with backing, and then mirror that state into the widget at viewDidLoad time, but that seems silly to have a property/state just for that one time trampoline like effect.
That is the other option really...
The underlying issue is that you are using the view (or a subview) as a container of knowledge that is really shouldn't be used for. A view is for displaying knowledge, not owning that knowledge.
More broadly, it's often attractive to abuse views and the properties they offer, such as view tags, cell selection state, etc, because it seems simple and wasteful to 'additionally' store that information somewhere else as well - but that doesn't make it correct to do so.
It is the responsibility of the controller to maintain the state information about what is being shown, so you should store that information in a property on the controller and maintain it there (use it to update the view, and don't query the view about its state).
I think you understand the problem fully. You might reduce the hacky-ness a little by forcing the view load in the from vc's prepareForSegue...
(void)controller.view;
But I think the answer you should take is your own, using a regular property. I don't think it's silly, since the default mode of a two-mode vc is a legit property. (think of UIViewController -hidesBottomBarWhenPushed)
Related
As I was thinking about all the incarnations of MVC in Cocoa, I figured I could make a custom class for every View in the application and fill it with a datasource and delegate - stuff primarily considered for Controller.
This way, instead of having the infamous Massive-View-Controller, I could chop off pieces of code and put them in separate files - one class for one View - along with their datasource and delegates.
Is it a good idea, or what are the downsides?
I'm afraid that your idea sounds like you will end up with a bunch of bloated views instead of a bunch of bloated controllers.
What I'd suggest is to consider the Single Responsibility Principle: an entity should have exactly one purpose or function. What's a view's function?
It's a representation, in code, of a region of the screen. That means it needs to do two things: draw to its area and register interactions with the area. Anything that's not absolutely essential to those two subtasks shouldn't be in the view class.
This is the idea of the "dumb view". It has no logic, no decisions to make. It just gets handed some data to render. And when it gets a tap or a click, it doesn't know what the input represents, or try to figure out what to do about it. It just knows the type of the interaction and tells another object.
That other object is the view's controller. A view controller's responsibility is to mediate between the view and the rest of the system. It gives the view its data. It also accepts messages from the view about input, and then reconfigures the view based on the result of those messages.
The view controller doesn't necessarily need to compute the result on its own, however. That's usually where view controllers start getting into "massive" trouble. The view controller should pick another object to help it get the new values that the interaction produces.
One possibility for this other object is the view model, in the MVVM structure. The view model is a display-focused representation of the raw data for the view. It transforms the information in the model into whatever format the view needs, and re-transforms or updates the data in response to input from the view controller.
Another idea is to split that responsibility even more finely, using a VIPER arrangement. Here the formatting of the data is handled by a "Presenter", and the transformation of the data is the job of an "Interactor".
It's possible to get into architecture astronaut territory here; blindly applying a complex structure can bite you if a view's needs are inherently very simple. But even if you choose not to formally apply one of these alternative patterns, a view controller needs other objects. You will want "controllers" with other specific jobs, that get messages from the view controller and pass data back.
The important thing is to keep in mind the original idea I mentioned: strive to make each type do one thing and do it well. That will keep your classes focused; easy to read, understand, and think about; and testable.
View doesn't compute it's data, it just displays them. However if you have custom control it can have some logic to compute it's inner data to trigger value change inside model.
Your approach is an overkill with unnecessary code.
So you are facing a problem where you need to set some values of your custom NSView or custom NSControl (e.g. NSButton title).
Some of available MVC solutions:
set the value inside controller manually and call setNeedsDisplay method inside within view on the changed property.
set the model object as the property of view. However, this introduces tight coupling but still is ok (so the view has to
know/import model class). +Include update/refresh method within view
use bindings to nsobjectcontroller. You don't need to set Class of nsobjectcontroller (that is needed only if you need additional
functionality for it like creating object automatically on add
method).
MVC pattern reminder
View has target action mechanism which is triggered in controller. Controller updates model (nothing else!). Model then has to propagate that it has changed and controller should react to it. It shouldn't react in the target action.
With bindings you skip the target action but the latter remains
You can combine 2 and 3.
If you are beginner forget about VIPER pattern. MVVM can help you to reduce size of your controllers.
How to use binding with NSObjectController:
#import <Foundation/Foundation.h>
#import "AppDelegate.h"
#import "Value.h"
#interface AppDelegate ()
#property (weak) IBOutlet NSWindow *window;
#property (weak) IBOutlet NSObjectController *objectController;
#property (strong) Value *value;
#end
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
self.value = [[Value alloc] init];
self.objectController.content = [self value];
}
#end
#interface Value : NSObject
#property NSString *value1;
#property NSString *value2;
#property NSString *value3;
#end
#import "Value.h"
#implementation Value
- (instancetype)init
{
self = [super init];
if (self) {
[self setValue1:#"Value1"];
[self setValue2:#"Value2"];
[self setValue3:#"Value3"];
}
return self;
}
#end
There is also an option to unload View Controllers with Coordinator objects. Shortly: each task in the app is managed by a Coordinator object, which manages its View Controllers.
There is the main Coordinator object, that is retained by application delegate and that retains all other Controllers. All the logic that doesn't belong in View Controller, is moved up to the Coordinator.
My app has a search view(search bar) which is used all over the app. I don't want to create duplicated code so I created a view controller called MySearchViewController to handle the search job, then I created a singleton object in AppDelegate. In every view controller, I added my search view like this:
- (void)viewDidLoad
{
MySearchViewController* search = [AppDelegate searchViewController];
[self.view addSubView:search.view];
}
My questions, Is it a good way? It's a singleton so it can be added to many views. Do I need to remove the view from last view before adding to current view?
Understand that you are mixing some concepts that are not necessarily related: avoid duplicated code and Singletons.
Wikipedia says this about singletons:
In software engineering, the singleton pattern is a design pattern that restricts the instantiation of a class to one object. This is useful when exactly one object is needed to coordinate actions across the system. The concept is sometimes generalized to systems that operate more efficiently when only one object exists, or that restrict the instantiation to a certain number of objects. The term comes from the mathematical concept of a singleton.
The most important characteristic of a singleton (in my humble opinion) is that the object is instantiated only once and every single place in your application will use the same instance. Well, to use your search feature everywhere and avoid duplicated code you don't need the search view to be instantiated only once, maybe the data that comes with it, but not the view itself.
Two better ways of achieving this:
1 - You can create a ViewController with your search and just embed this on the other views using a Container View, you can use blocks or a delegate protocol to communicate between your controller and the view that is embedding it.
2 - You can create a Parent class of the ViewController that will include the search bar, like a SearchViewController and all the other viewControllers that needs the same feature will inherit from it.
The singleton could be useful if you are planing to share the same search data and text between all the ViewControllers of the application, but it would be a singleton only with these information, the UISearchBar and all other view elements should not be part of the singleton.
Ideally, you should instantiate a fresh instance of MySearchViewController every time when you want to add it to another view to avoid problems.
Do I need to remove the view from last view before adding to current view?
Its not required to remove it from previous super view because whenever you add this singleton MySearchViewController's view to some other view, it will automatically gets removed from last super view and now its super view is your new view where you have added it.
If you want to add a view from a different view controller, your view controller has to be that view controller's parent view controller:
- (void)viewDidLoad
{
MySearchViewController* search = [AppDelegate searchViewController];
[self addChildViewController:search];
[self.view addSubView:search.view];
}
also, make sure that when the search.view is added, it is already initialised.
Why you do not use NSObject class ?, i do not know your requirement , but if you want to store latest updated value in whole project(in execution) then you should use the singleton, but if you do not want to store value (i mean one result for whole project) then you should use NSObject derived Class. advantage is singleton consumes memory so memory will be wasted. NSObject class will be reusable and only allocated when it is required and then ARC will take care of all things. If you want to know how to create NSObject and use of it then you can give me reply.
Here is some code to load a XIB as part of a custom object with the object gets initialized.
Why are you not creating custom search component for search?
you can use this component all over the app.
also this is not creating duplicat code.
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
// Initialization code
[[[NSBundle mainBundle] loadNibNamed:#"SearchView" owner:self options:nil] objectAtIndex:0];
[self addSubview:self.view];
self.frame = self.view.frame;
}
return self;
}
Please check below code. Hope this is work for you.
- (void)viewDidLoad
{
if ([self.view viewWithTag:123456]) {
[[self.view viewWithTag:123456] removeFromSuperview];
}
MySearchViewController* search = [AppDelegate searchViewController];
search.view.tag = 123456; // give a any random tag to view
[self.view addSubView:search.view];
[self addChildViewController:search];
}
Please make sure given tag is not assign to other object except search.view in self.view.
Thanks
I'm trying to change the title of a button after I call back from a notification but it doesn't respond at all. I checked it's not nil and checked the text Im' assigning and all is good. I made the property type strong instead of weak but no success.
- (void) setButtonTitleFromSelectedSearchResult:(NSNotification *)notif
{
[self popController];
self.sourceMapItem = [[notif userInfo] valueForKey:#"SelectedResult"];
NSLog(#"The Selected Result is: %#", self.sourceMapItem.name);
//Testing
NSLog(#"%#", self.fromButton); // check it's not nil
[self.fromButton setTitle:self.sourceMapItem.name];
}
With WatchKit, if a user interface element isn't currently visible, it cannot be updated. So, if you've presented another interface controller "on top", you can't update any of the presenting controller's interface elements until you've dismissed the presented controller. At that point, you can safely update the presenting controller in its willActivate method.
SushiGrass' method of passing blocks is certainly one valid approach. In my testing, however, I ended up having to manage multiple blocks, and many of the subsequent blocks reversed what earlier queued blocks had accomplished (for example, first changing a label's text to "foo", then "bar", then "foo" again. While this can work, it isn't optimal.
I'd suggest that anyone who is working on a WatchKit app takes a moment to consider how they want to account for off-screen (i.e. not-currently-visible) interface elements. willActivate is your friend, and coming up with a way to manage updates in that method is worthwhile if you're moving from controller to controller.
For what it's worth, I've encapsulated a lot of this logic in a JBInterfaceController subclass that handles a lot of this for you. By using this as a base class for your own interface controller, you can simply update your elements in the added didUpdateInterface method. Unfortunately, I haven't yet had the time to write proper documentation, but the header files and sample project should get you going: https://github.com/mikeswanson/JBInterfaceController
I'm using latest XCode 6.3 and below code working with me.
self.testBtn is bind with Storyboard and its WKInterfaceButton
I also have attached screenshot with affected result.
I'm setting initial text in - (void)willActivate
- (void)willActivate {
[super willActivate];
[self.testBtn setTitle:#"Test"];
[self performSelector:#selector(justDelayed) withObject:nil afterDelay:5.0]
}
-(void)justDelayed
{
[self.testBtn setTitle:#"Testing completed...!!"];
}
If you're using an IBOutlet for the property fromButton be sure that is connected to WKInteface on the storyboard, like below:
I solved this kind of issue by creating a model object that has a property that is a block of type () -> (Void) (in swift). I create the model object, set the action in the block that I'd like the pushing WKInterfaceController to do on completion, and finally pass that model object in the context to the pushed WKInterfaceController. The pushed WKInterfaceController holds a reference to the model object as a property and calls it's completion block when it's done with whatever it needs to do and after func popController().
This worked for me for patterns like what you are describing along with removing rows on detail controller deletion, network calls, location fetches and other tasks.
You can see what I'm talking about here: https://gist.github.com/jacobvanorder/9bf5ada8a7ce93317170
I'm trying to category UIViewController to override viewWillAppear:. But getting this warning.
Category is implementing a method which also be implemented in primary
class
#implementation UIViewController (ViewWillAppearCategory)
-(void)viewWillAppear:(BOOL)animated
{
//.........
}
#end
I want to do some stuff during view appear in all screen, So I don't want to touch in all screen. that's why, go with category.
I may implement some method in sub class and I can call that method in all VC(all Screen). But I don't want this. It automatically invoke in view will appear call. Is this any idea to do this or did any mistake in above?
Note: This code will only appear in development phase for some testing purpose. So I'll remove this code when go with app store. So It should be easier task during removal, that is I won't touch all screen. I won't keep this code during submission to app store.
In such cases you must try Method Swizzling, a very nice formed concept which allows you to change the implementation of an existing selector.
For more details and code please visit the link below.
http://nshipster.com/method-swizzling/
categories are for adding new methods, not overriding existing ones. Maybe make a subclass of UIViewController, say, MyUIViewController, with this code:
-(void) viewWillAppear:(BOOL) animated {
// do your "category" stuff
}
then make all your other UIViewControllers subclasses of MyUIViewController with this code:
-(void) viewWillAppear:(BOOL) animated {
[super viewWillAppear:animated];
// rest of code for this class
}
I understand the reasons why you want to have a simple solution to test something on all screens and remove it easily, however:
You can not call super in a category, and not calling [super viewWillAppear:] may have unexpected results depending on the class and its particular implementation.
Swizzling methods is a hack and as you'll remove it from your final version, your testing version becomes useless as it may behave very differently.
On the other hand creating a UIViewController superclass where you properly override viewWillAppear: is not that complicated:
The code will belong only to a single class. No need to repeat/maintain code for every "screen".
You only need to change the other controllers' superclass and Nibs or Storyboards references once.
You can keep the superclass for both testing and release and the behavior will be similar.
You can do so many more things in a superclass than in a category.
Ultimately it would be interesting to know what are you trying to achieve. You could probably achieve similar things by implementing a UINavigationControllerDelegate and keep track of controllers getting pushed and popped.
As for viewWillAppear documentation:
This method is called before the receiver’s view is about to be added
to a view hierarchy and before any animations are configured for
showing the view. You can override this method to perform custom tasks
associated with displaying the view. For example, you might use this
method to change the orientation or style of the status bar to
coordinate with the orientation or style of the view being presented.
If you override this method, you must call super at some point in your implementation.
Again, you can't do that from a category.
As you are saying the code is going to be executed only in debug mode. Then why do you worry about warnings let the warning come you continue your work when it comes to release you remove your Category.
If you don't even need to see the warning your go with your same answer like
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation"
-(void)viewWillAppear:(BOOL)animated
{
NSLog(#"I get callback here too");
}
#pragma clang diagnostic pop
But I would say to go for subclassing because removing the existing class is also not that hard in XCode tool.
What you want to achieve defeats the purpose of a Category. However, there's another way aside from subclassing UIViewController but you have to touch the viewWillAppear method for each controller.
//UIViewController+CustomCategory.h
#interface UIViewConctroller (CustomCategory)
- (void)performCustomization;
#end
//UIViewController+CustomCategory.m
#implementation UIViewController (CustomCategory)
- (void)performCustomization {
// Do custom stuff…
}
#end
Then in each controller
//MYViewController.m
#import "UIViewController+CustomCategory.h"
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self performCustomization];
}
I'm currently implementing automatic state preservation/restoration in an iOS6-only app.
For a restoration of a table view, I added the UIDataSourceModelAssociation protocol to my table view controllers and implemented
- (NSString *)modelIdentifierForElementAtIndexPath:(NSIndexPath *)idx inView:(UIView *)view
and
- (NSIndexPath *)indexPathForElementWithModelIdentifier:(NSString *)identifier inView:(UIView *)view
When pressing the home button, the state preservation methods, including modelIdentifierForElementAtIndexPath:iView:, are getting called as expected and return valid identifier strings for the given index paths.
When killing the app and relaunching it, state restoration works more or less. I.e. the app re-opens the correct table view. However, the table view is always scrolled to the top, even when it was scrolled to another position before.
Here's the implementation of the UIDataSourceModelAssociation methods in my table view controller. Nothing fancy going on in there (the NdlFriend::accountUidproperty returns a unique identifier string for a given NdlFriend record):
#pragma mark - UIDataSourceModelAssociation
- (NSString *)modelIdentifierForElementAtIndexPath:(NSIndexPath *)idx inView:(UIView *)view
{
NSString* identifier = nil;
NSArray* content = self.contentArray;
// Sometimes idx might be nil...
if(idx && idx.row<content.count)
{
NdlFriend* friend = content[idx.row];
identifier=friend.accountUid;
}
return identifier;
}
- (NSIndexPath *)indexPathForElementWithModelIdentifier:(NSString *)identifier inView:(UIView *)view
{
NSIndexPath * indexPath=nil;
NSArray* content = self.contentArray;
NSInteger count = content.count;
for(NSInteger i=0;i<count;i++)
{
NdlFriend* friend = content[i];
if([identifier isEqualToString:friend.accountUid])
{
indexPath = [NSIndexPath indexPathForRow:i inSection:0];
break;
}
}
return indexPath;
}
I set break points in both methods.
To test the methods, I opened the table view and scrolled down a little bit. Then, when pressing the home button:
modelIdentifierForElementAtIndexPath:inView: gets called once, with the index path of the top most visible row. The method returns the correct uid for this row.
So far so good.
Then I stop and relaunch the app. Here's what happens (I'm especially puzzled by the first hit break point):
modelIdentifierForElementAtIndexPath:inView: gets called, with nil as index path (the view argument contains the correct pointer of the table view).
indexPathForElementWithModelIdentifier:inView: gets called with a valid identifier string (and a valid index path is returned by the method).
indexPathForElementWithModelIdentifier:inView: gets called again (with the same identifier string).
The table view is refreshed, but scrolled to the very top.
Does someone know, why the restoration of the scroll position fails? Does maybe the call of modelIdentifierForElementAtIndexPath:inView: with nil as indexPath has something to do with it (or is this normal behavior).
There is a bug in iOS 6 regarding state restoration of Table Views in Navigation Controllers.
You can see the open radar here: rdar://13438788
As you can see, it's a duplicate, so Apple are aware of this.
Also, see this next link, the guy who posted that open radar also did this blog post, it has the suggested workarounds that the Apple Engineers told him.
It makes the state preservation/restoration for me not such an enjoyable feature to implement, although remember, this is for your users ! So you should just do the workaround anyway.
Note there are 2 workarounds, one for the table view's view information to restore (scroll offset for example), and another workaround for the use when implementing UIDataSourceModelAssociation which is your case.
http://useyourloaf.com/blog/2013/04/07/bug-table-view-state-not-restored-when-embedded-in-navigation-controller.html
I don't think the problem you're having of the table view's scroll position resetting has to do with the UIDataSourceModelAssociation methods.
I believe there's a bug with a table view controller embedded in a nav controller, that causes it to reset its scroll position after restoration. As I understand it, if the cells in your table view don't reorder, you don't need to implement the UIDataSourceModelAssociation methods and you should get the scroll position "for free" (i.e. as long as you've opted into state preservation and set the restorations IDs). I can't really confirm this explicitly from the documentation, except to point out that UITableView descends from UIScrollView, which saves its scroll position. I've tested that if you set the table view controller to be the root controller, or embed a table view controller in a tab bar controller, then the scroll position is restored.
I've filed a bug report, you should too, if you haven't already.
I agree with Aky and this might indeed be a bug in iOS 6. See my answer on a related question: https://stackoverflow.com/a/14567551/322548
Make sure that you're not performing an asynchronous fetch on your data. If you are fetching your data from viewDidLoad, make sure that you use a [myManagedObjectContext performBlockAndWait:^{}] call instead of a [myManagedObjectContext performBlock:^{}].
It may be that you have a race condition where self.contentArray is empty when indexPathForElementWithModelIdentifier gets called.