Optimal way to setup UIView in UIViewController? - objective-c

I'm wondering if my method to setup my UIViewController is optimal or just plain stupid.
I have typedef'ed an enum with some categories. Let's say 6 different categories.
So depending on which category is the selected one. My UIViewController have a switch which will call different method to setup my UIView according to the selected category.
Just wondering if this is a good method to do this, or should I consider creating 6 different UIViewControllers?
A discussion with pro and cons is very much appreciated.
Thanks.
They are basically the same.
Sample code:
switch (self.category) {
case vegetables:
recipe = [[[WebServices sharedInstance].recipeDictionary objectForKey:self.chosenCategory] objectAtIndex:4]; //Needs to be made random
descriptionText.text = recipe.recipeDescription;
[self setupText];
[self setupVegetablesView];
break;
case dairy:
recipe = [[[WebServices sharedInstance].recipeDictionary objectForKey:self.chosenCategory] objectAtIndex:4]; //Needs to be made random
descriptionText.text = recipe.recipeDescription;
[self setupText];
[self setupDairyProductsView];
break;
- (void)setupVegetablesView
{
descriptionText.textColor = [UIColor colorWithRed:0/255.0 green:103/255.0 blue:55/255.0 alpha:1];
background.image = imageBackgroundVegetables;
topBar.image = topBarForVegetables;
subtitle.image = subtitleImageVegetables;
subtitleLink.image = subtitleLinkBarVegetables;
...
}

Depends on your situation. If the view controllers are similar, than this makes sense. But if they are completely different from each other, use separate subclasses.

I would implement it as following
• i would several UIView derived class each one for the type of UIView that i need
For example, i would have VegatableView and DiaryView
• each one of these view will have the same base class of for example MyBaseView
• MyBaseView will have a function called setup this function will need to be implemented in each of my derived classes (vegetable and diary)
• depending on your enum i would create one of these concrete classes and call the setup function
Example:
switch (self.category) {
MyBaseView recipe;
case vegetables:
//Create an instance of VegetableView
recipe = [[VegetableView alloc] init];
break;
case dairy:
//Create an instance of DiaryView
recipe = [[VegetableView alloc] init];
break;
}
//Call setup for the created view
[recipe setup];
//Setup function in vegetableView.m
- (void)setup
{
//Do some vegetable setup stuff
}
//Setup function in diaryView.m
- (void)setup
{
//Do some diary setup stuff
}
In this way, i would minimize the different code, i would make the parameter equal for both the types of view
Also adding new views will be rather easy, just subclass MyBaseView and implement a setup function that is specialized for your new view
Hence increase the objects decoupling and reducing complexity

Related

Using RACCommand with MVVM pattern, sending parameters to ViewModel

I'm using ReactiveCocoa framework at my app for the power using MVVM design pattern.
So for every Controller, I have a ViewModel. and the Controller is binded to his ViewModel.
UIButton binding will look like so:
#implementation HomeController
-(void) bindViewModel {
self.viewHeader.buttonSide.rac_command = self.viewModel.execiteFullPortfolio;
}
It all works well, But when i would like to pass parameters to the ViewModel, I'm not sure what is the right way to do so...
Say I have a UICollectionView of Stocks, and every click on a specific stock, I would like to navigate to thats stocks profile page.
That logic should be done at the ViewModel, But how do i get the stock passed with the RACCommand?
What I'm currently doing is :
#implementation HomeController
-(void) bindViewModel {
__unsafe_unretained HomeController *weakSelf = self;
self.viewPortfolioPusherView.scrollGainView.blockSelect = ^ (STStock *stock){
weakSelf.viewModel.selectedStock = stock;
[weakSelf.viewModel.executeGoToStock execute:[RACSignal empty]];
};
}
#implementation HomeViewModel
-(void) initialization {
self.executeGoToStock = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf moveToSelectedStock];
});
return [RACSignal empty];
}];
}
-(void) moveToSelectedStock {
[self stockProfileControllerLazy];
self.stockProfileController.stock = self.selectedStock;
[Navigator pushController:self.stockProfileController fromController:[self.delegate controller]];
}
I'm sure this is not best practice! So I'm asking, What is??
Thanks .
Why not just pass the STStock instance into the call to execute on the command, rather than an empty signal?
[weakSelf.viewModel.executeGoToStock execute:stock];
Then:
self.executeGoToStock = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(STStock *stock) {
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf moveToSelectedStock:stock];
});
return [RACSignal empty];
}];
You obviously need to modify moveToSelectedStock to take a parameter as well. However, I'd go a bit further an implement an RACCommand on your Navigator that does that. Furthermore, I'd make a separate view model for an instance of STStock rather than a collection. So, when you select a stock, it might look something a little more like this:
StockViewModel *viewModel = [[StockViewModel alloc] initWithStock:stock];
[[Navigator pushViewModel] execute:viewModel];
This obviously omits a few details. For example, my navigator maps view model classes to controller classes. When a view model is pushed, it creates the corresponding controller, and binds the view model to it.

Passing Level parameter when transition to next Scene?

I'm trying to figure out the best way to pass a level parameter between Scenes using Spritebuilder and Cocos2D in Xcode.
I'm using the standard code below to transition between scenes.
[[CCDirector sharedDirector] replaceScene:[CCBReader loadAsScene:#"Gameplay"]];
Any help will be much appreciated.
Assuming that the Gameplay.ccb has a GameplayClass assigned as its custom class, and that class has a property named currentLevel, you can access the instance and assign the level as follows:
CCScene* theScene = [CCBReader loadAsScene:#"Gameplay"];
GameplayClass* game = (GameplayClass*)theScene.children.firstObject;
game.currentLevel = 3;
[[CCDirector sharedDirector] replaceScene:theScene];
Note that by the time currentLevel is assigned the GameplayClass will have already run its init and didLoadFromCCB methods since that happens during loadAsScene. If you need further init processing override onEnter in GameplayClass:
-(void) onEnter
{
[super onEnter]; // must call super
switch (self.currentLevel)
{
// other switches omitted...
case 3:
// your level 3 code here
break;
}
}
In my game (made with Cocos2d 2.0 + CocosBuilder) I added a class method loadWithLevelID:levelID to the class GameObjectLayer that manages the gameplay elements:
#implementation GameObjectLayer {
G1LevelID * _levelID;
}
// load a Luminetic Land game object layer
+ (instancetype)loadWithLevelID:(G1LevelID*)levelID {
NSString * levelFileName = ... builds levelFileName from levelID;
GameObjectLayer * gol = (GameObjectLayer*) [CCBReader nodeGraphFromFile:levelFileName];
[gol setLevelID:levelID];
return gol;
}
- (void)setLevelID:(G1LevelID*)levelID {
_levelID = levelID;
}
So now I can create a GameObjectLayer typing
GameObjectLayer * gol = [GameObjectLayer loadWithLevelID:levelID];
In general, adding a "load" method to the classes that map ccbi files offers the following benefits:
Only the class knows the name of its related ccbi file.
The ccbi file to create a given class is referenced only once in the entire project.
All the logic to set up an object (e.g. of type G1GameObjectLayer) is inside the object itself.
I think you can follow a similar approach with your CCScene subclass.

Typhoon - How do I inject a UIView defined in a xib file?

I want to inject a view into my view controller so that I can inject a mock view in my unit tests (WPDependencyInjectorImplementation is my TyphoonAssembly subclass).
My ideal loadView method would look like the following:
- (void)loadView {
WPDependencyInjectorImplementation *injectorAssembly = (WPDependencyInjectorImplementation *) [TyphoonAssembly defaultAssembly];
self.view = [injectorAssembly someView];
}
I'm not sure what the definition for this would look like or whether it's possible, given that the code for creating a view from a xib is the following:
NSArray *views = [[NSBundle mainBundle] loadNibNamed:#"WPSomeView" owner:nil options:nil];
return [views firstObject];
That's right you just override loadView, rather than looking up the view from the container, as you've shown, you should provide the view via an initializer or property setter. And then in loadView, set it to the injected view as follows:
- (void)loadView
{
[self.view = _myInjectedView]; //Do this rather than looking it up via the TyphoonFactory - easier to test.
}
If you do it this way:
You can refer to the view via it's actual type, rather than down-casting from UIView
It'll be really simple to mock-out in a pure unit test. (No need for TyphoonPatcher, swizzling, etc).
Here's an example:
- (id)userDetailsController
{
return [TyphoonDefinition withClass:[UserDetailsController class] initialization:^(TyphoonInitializer* initializer)
{
initializer.selector = #selector(initWithSession:userDetailsView:);
[initializer injectWithDefinition:[self session]];
[initializer injectWithDefinition:[self userDetailsView]];
}];
}
- (id)userDetailsView
{
return [TyphoonDefinition withClass:[UserDetailsView class]
properties:^(TyphoonDefinition* definition)
{
//circular dependency. Can also be set within VC.
[definition injectProperty:#selector(delegate)
withDefinition:[self userDetailsController]];
[definition injectProperty:#selector(sideMargin)
withValueAsText:#"${view.field.default.side.margin}"];
}];
}
Injecting From a Xib
We don't actually have a Xib factory we can provide you yet. It should be a quick job to define one using a similar pattern to the object that emits a theme here, so you'll have a component in the DI container for each of your Xib-based views, and just inject that directly.
Alternatively you could use our new TyphoonFactoryProvider.
If you get stuck, please let us know and one of us will find some time to create and push that Xib-view-factory for you.

Singleton Design Implementation

As per my previous question, here, I've adapted my Data Controller class over to use a singleton design pattern so that I can use it only once across multiple views. However I do have a couple question I can't seem to find the solution too.
Firstly I'm not exactly sure how to call the class/object in the two views to make it work, and secondly I've made the initialisation method global with + but do I need to do this with each of the methods?
The initialisation of of the class that I want to be able to share across the views, in order to share the data, is
static SpeecherDataController *_instance = nil; // <-- important
+(SpeecherDataController *)instance
{
// skip everything
if(_instance) return _instance;
// Singleton
#synchronized([SpeecherDataController class])
{
if(!_instance)
{
_instance = [[self alloc] init];
// NSLog(#"Creating global instance!"); <-- You should see this once only in your program
}
return _instance;
}
return nil;
}
The class uses three Mutable Arrays as the main content which need to be both set and read in the two views.
If I understand your questions correctly, I think the answers are:
You can use something like:
SpeecherDataController * localReference = [SpeecherDataController instance];
and then later:
[localReference someMessage:param]; // or ...
localReference.property = whatever;
No, the methods on your SpeecherDataController class do not also need to be made class methods (i.e., they do not need to have the + prefix, they can use - if you want to access ivars within them).
Note: I think you want to replace [[self alloc] init]; with [[SpeecherDataController alloc] init]; in your implementation of instance.
(Also, note: I was unable to follow your link to "here" above to see your previous question. So my apologies if I misunderstood something.)

event scope

Given
#interface Canvas:NSView {
NSNumber * currentToolType;
...
}
declared in my .h file
and in the .m file
- (id)initWithFrame:(NSRect)frame {
self = [super initWithFrame:frame];
if (self) {
currentToolType=[[NSNumber alloc]initWithInt:1];
}
return self;
}
and further down
-(void)mouseUp:(NSEvent *)event
{
NSLog(#"tool value in event: %d",[currentToolType intValue]);
//rest of code
}
-(NSBezzierPath *)drawPath:(NSRect)aRect
{
NSLog(#"tool value in draw: %d",[currentToolType intValue]);
//rest of drawPath method code that uses the value of currentToolType in a switch statment
}
-(IBAction)selectToolOne:(id)sender
{
[currentToolType release];
[currentToolType = [[NSNumber alloc]initWithInt:0];
}
-(IBAction)selectToolTwo:(id)sender
{
[currentToolType release];
[currentToolType = [[NSNumber alloc]initWithInt:1];
}
The action methods are the only place where currentToolType is changed. But, for some reason, it seems to be a different instance of currentToolType in the mouseUp. I did not write (or synthesize) accessors for the var as it is used only by itself. I noticed that initWithFrame is called twice - I'm assuming it's for the parent window and the NSView?
What am I missing?THANKS!
This is an XCode generated Document based app using COCOA and Obj-C. I'm new at both.
You mention that initWithFrame: is called twice. Your initWithFrame: should only be called once (unless you happen to have two Canvas views).
Is it possible you have the Canvas view in your nib/xib file and are also creating another in code (with alloc/initWithFrame:)?
In which case you have two Canvas objects. You probably have one hooked up to your controls and the other one is in the window (and thus responding to the mouseUp: and it is giving you the same value every time).
If you have the Canvas view setup in IB, you can fix this problem by removing your code that is creating the second one.
You've probably run in to a special case: NSNumber could have cached instances to represent commonly-used numbers.
Two observations, though:
You're wasting a whole lot of memory using NSNumber when you could be simply using NSIntegers or maybe an old-fashioned enumerated type, completely avoiding the object overhead.
You never actually showed your code for when you look at the instances of NSNumber; without it, there's not really enough information here to answer your question.