How to change the diagrams displayed in the same view? - ios

How to change the diagrams displayed in the same view?
I have a view inside a view controller.
This view has another view inside it, to which I have allocated a custom class (sub-classing UIView).
This custom class has the code to draw interactive diagrams in this view.
The interactive code is operated by sliders in the main view.
I have all this in interface builder.
What if I want to draw a completely different diagram in this view?
I would like to be able to allocate a new class to this view, with a different set of drawing code? But how?

There are many ways to do this, but changing the class at runtime is not advisable
Here are a few suggestions:
1 / Replace customView1 with a different view instance of the right type in the same location as your first view...
self.customView2 = [[CustomView2 alloc] initWithFrame:self.customView1.frame];
[self.view addSubView:self.customView2];
[self.customView1 removeFromSuperView];
This example uses a distinct property for each of the swapped subviews, but you could use a single property just to refer to the current subview - this could help link up your sliders to do the right thing to the diagrams. If you are doing a lot of this you will need to think about memory issues - when customView1 has gone, will you be reusing it? You can keep it hanging around in a (strong) property (faster, needs more memory), or create a new one each time (slower, needs less memory).
2 / if you want to toggle between them, you could place both in Interface Builder and toggle their hidden properties or their order in the view hierarchy (self.view.subviews array). Saves having to constantly recreate the views.
3 (better...) / Keep to a single subclass of UIView and use properties to affect the diagram that gets drawn...
//CustomView.h
#property (nonatomic,assign) BOOL drawDiagram1;
#property (nonatomic, assign) BOOL drawDiagram2;
//CustomView.m
- (void)drawRect {
if (drawDiagram1) [self drawDiagram1];
if (drawDiagram2) [self drawDiagram2];
}
- (void) drawDiagram1 {
//drawDiagram1 code here
}
- (void) drawDiagram2 {
//drawDiagram2 code here
}

Related

Is scoping the controller's code under a custom view a good cocoa design pattern?

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.

Can the same ViewController.view be added as subview to different views as a Singleton?

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

Programmatically reinitializing a UIView causes it to not update

I have a UIViewController which has a small scrollable window, in which I put a custom UIView called "TreeView". TreeView is blank, but has child classes of specific trees. In my code, in viewDidLoad, I am doing the following:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
CGRect treeFrame = self.treeView.frame;
NSLog(#"passing tree frame of origin %f %f, size: %f, %f", treeFrame.origin.x, treeFrame.origin.y, treeFrame.size.height, treeFrame.size.width);
self.treeView = [[GelbMotiveView alloc] initWithFrame:treeFrame];
}
Then, in my test child, I load it's .xib file (successfully) and see that UIViews and subviews are added to the child. However, these views are never rendered, even if I call setNeedsDisplay. Instead, the original TreeView is rendered (I gave it a red background so I would know for sure). I can put a break point in drawRect in the test child class and it's never called.
What am I doing wrong?
Am I just doing something completely weird? Should subclasses of UIView not have their own subclasses? Is there a better way to programmatically swap out a small section of a UIViewController's screen? My intent is for the children of TreeView to render a tree/graph and make it intractable (buttons, etc, for nodes). Is there a better way to do this?
I take it that treeView is an IBOutlet and loaded from a nib?
If that is the case, the mistake you are making is reinitializing that pointer with initWithFrame: which wipes out your reference to the original view and creates a new one that isn't hooked up to anything. You can change the frame and change properties on it but you shouldn't initialize it again.

Connect two labels to one outlet

Now I understand that this question has been asked before, but the answers were unsatisfactory. My issue is that I have a view controller with a view and stuff in it including a label. I added a bunch of code for it and now I'm expanding on it. I now have an issue where I've decided to add another UIView to my interface and it has a label and that label is going to function EXACTLY like a label I have in my first UIView. My problem is that I don't want to have to go in my view controller method and add another line of code each time I manipulate that first label. Is there anyway I can link another label to my initial IBOutlet I have set for my first label? Or do I have to go in my code and add an extra line of code everytime I manipulate that first label?
It depends on what you want to do to that label. If you're looking to change some of the attributes of the label in the same way (e.g., font, text colour, alignment) then you can put both labels in an IBOutletCollection and iterate over the collection in your view controller.
If you want to have different data in the label, but other attributes the same, then you'll need a separate IBOutlet for that label.
You can combine the two techniques as well. e.g.
(interface)
#property (weak, nonatomic) IBOutlet UILabel *firstName;
#property (weak, nonatomic) IBOutlet UILabel *lastName;
#property (strong, nonatomic) IBOutletCollection(UILabel) NSArray *labels;
(implementation)
- (void)viewDidLoad {
[super viewDidLoad];
for (UILabel *aLabel in self.labels) {
// Set all label in the outlet collection to have center aligned text.
[aLabel setTextAlignment = NSTextAlignmentCenter;
}
[self.firstName setText:#"First Name"];
[self.lastName setText:#"Last Name"];
}
Basically the simple answer is no. Whether you use outlets or an outlet collection or tags or whatever, you ultimately have one reference in your code to one label in your interface, and another reference in your code to another reference in your interface. You can compress your mode of expression so as to cycle readily through those references (as suggested in a different answer), but the basic fact is inescapable that, ultimately, the only way to "talk to" a label is through the one reference that points to that label and to that label alone.
The only way of getting around that is not to use direct references at all. For example, a single message can be sent to multiple recipients by using an NSNotification. So you could have two instances of some UILabel subclass of your own, and "shout" to both instances simultaneously by posting a notification from your view controller - the notification is then automatically passed on to both labels, because you have arranged beforehand for them to register for it.
Similarly, another alternative is that you could use key-value observing so that a change in your view controller is automatically propagated to both labels automatically because they "observe" the change, meaning they are sent notifications - really just an inverted form of NSNotification. (If this were Mac OS X, you could make a simpler, safer version of this arrangement by using "bindings".)
However, I really cannot actually recommend that approach. The truth is that we still live in an excruciatingly primitive world of text-based programming, one line at a time, one command at a time, one reference at a time, and we must just bite the bullet and get on with it.
Swift 3, Xcode 8
Create a prototype cell with objects
then add another prototype
It will copy the objects from the first prototype cell.
The new objects will be connected to the same IBOutlet
Also, copy and pasting objects maintains IBActions, but does not maintain IBOutlets.
I hope this answers your question, as none of the other answers had this work around.

Multiple UIViewControllers on screen at the same time

I have read in multiple places that only one UIViewController should be on screen at a time. But I don't quite understand how to accomplish what I need otherwise.
Imagine the weather app. There is a single view controller with a scroll view, to which multiple views are added (the weather panels). Presumably these are all added and managed by the main UIViewController, which is also responsible for scrolling, etc.
But imagine each of those weather panels was instead a CarView, each with data about a specific type of car, and some controls to edit that data.
Wouldn't it make sense to have a series of CarViewControllers, each with a Car property that they can manipulate? Each CarViewController would be responsible for it's car data object, it's view, and glueing them together, and the main view controller would simply be responsible for adding each carViewController.view to its scrollview.
Isn't this better for re-usability and encapsulation? Not to mention more convenient.
I think it just comes down to whether it will make your life easier or not. One alternative I like to do is to just write composite UIView subclasses specifically for displaying a "conceptual" model - like a car - and then write categories on them to populate the view information using a specific model implementation. That way you can re-use the view when changing your model but still keep some of the logic from cluttering up the view controller. I try to reserve a view controller for something like radically different views toggled with a UISegmentedControl or something.
Edit: an example of a UIView and its populating category.
#interface CarView : UIView
#property (strong) UILabel *modelLabel;
#property (strong) UILabel *makeLabel;
#property (strong) UILabel *yearLabel;
//etc
#end
Then you have a model-specific category, that while a category on the view really fits more into the controller layer; while a little bit of a breach of MVC I think it works nicely from a responsibility assignment standpoint, doesn't couple your main view implementation to any data implementation, and keeps your view controllers leaner, so I think it's worth the tradeoff.
#interface CarView (CarEntityPopulating)
- (void)populateFieldsWithEntity:(NSManagedObject *)entity;
#end
#implementation CarView (CarEntityPopulating)
- (void)populateFieldsWithEntity:(NSManagedObject *)entity
{
self.modelLabel.text = [entity valueForKey:#"name"];
self.makeLabel.text = [[entity valueForKey:#"make"] valueForKey:#"name"];
self.yearLabel.text = [[entity valueForKey:#"year"] stringValue];
//etc....
}
It seems like this now explicitly supported in iOS 5 using a container view controller.
There are not a lot of resources yet that discuss it, but this WWDC session is useful.
This question also provides some resources.

Resources