iOS - Implementation of MVC-Pattern without Interface Builder - objective-c

I'm creating a little MVC-sample-project as an iPhone-application in XCode, which is built completely with code and therefore doesn't use Interface Builder. First I'd like to show you the code I have so far.
Controller
The controller instantiates the model and the view and also contains a function which demonstrates the independency between model and view:
ViewController.h
#import <UIKit/UIKit.h>
#import "MainView.h"
#import "ProjectModel.h"
#interface ViewController : UIViewController
#end
ViewController.m
#import "ViewController.h"
#interface ViewController ()
{
ProjectModel *model;
MainView *myMainView;
}
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
model = [[ProjectModel alloc] init];
myMainView = [[MainView alloc] initWithFrame:CGRectMake(0, 0, 320, 468)];
[self.view addSubview:myMainView];
//test function to illustrate that view and model are independent
[self calculate];
}
- (void)calculate
{
int result = [model operationWithNumber:3 andAnotherNumber:5];
[myMainView showResult:result];
}
#end
Model
The class ProjectModel is responsible for the model of the project and is in this example for the sake of simplicity only responsible for summing up two numbers:
ProjectModel.h
#import <Foundation/Foundation.h>
#interface ProjectModel : NSObject
-(int)operationWithNumber:(int)number1 andAnotherNumber:(int)number2;
#end
ProjectModel.m
#import "ProjectModel.h"
#implementation ProjectModel
-(int)operationWithNumber:(int)number1 andAnotherNumber:(int)number2
{
return (number1 + number2);
}
#end
View
The view-class creates all the elements of the view and contains a function which displays the result of a calculation in a label.
MainView.h
#import <UIKit/UIKit.h>
#interface MainView : UIView
{
UILabel *lblResult;
}
- (void)showResult:(int)result;
#end
MainView.m
#import "MainView.h"
#implementation MainView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
UILabel *lblTitle = [[UILabel alloc] initWithFrame:CGRectMake(20, 20, 280, 50)];
lblTitle.text = #"This is my View";
[self addSubview:lblTitle];
lblResult = [[UILabel alloc] initWithFrame:CGRectMake(20, 200, 280, 50)];
lblResult.text = #"Result will be displayed here.";
[self addSubview:lblResult];
}
return self;
}
- (void)showResult:(int)result
{
lblResult.text = [NSString stringWithFormat:#"Resultat: %d", result];
}
#end
My Question:
I hope you understood the code so far. Based on the code above, I'd like to implement a button in the view-class which should calculate and display two numbers when the users clicks this button. Thus when the user clicks the button, the calculate-function in the ViewController should be called. I created a button with the following code in MainView.m:
UIButton *btnCalculate = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[btnCalculate setFrame:CGRectMake(20, 90, 280, 50)];
[btnCalculate setTitle:#"Calculate" forState:UIControlStateNormal];
[btnCalculate addTarget:self action:#selector(calculate:) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:btnCalculate];
The problem is this line in the code above:
[btnCalculate addTarget:self action:#selector(calculate:) forControlEvents:UIControlEventTouchUpInside];
how can I add a function from the controller as the action of the button. I know that the target should not be self, since the function should be called in the ViewController, but I have no idea how I could do that. Could anyone help me and tell me how I can solve this problem?
The only solution that I can see right now is that the complete GUI is created in the ViewController directly. But I don't think that this is a nice solution, since the main purpose of MVC is to avoid the mix of controller and view code in the same class.
Also I'm wondering whether this code generally conforms to the MVC-pattern propagated by Apple, since I'm pretty new to this design-pattern. I'd really appreciate a short feedback for this code.

Don't set the target in the view class. For MVC, the view should not explicitly know about the controller. The view class should have a public property to expose the button and then the controller can update the button to add the target.
Other than that it looks like you understand the point of MVC.
This line does confuse a bit [self.view addSubview:view]; as it appears to be trying to add the view as a subview of itself...

The MVC pattern is a nice thing, but I don't think it is necessary for a program like yours to have that degree of separation. Also the button can be in the view class since it is a part of that pattern along with calculate function.

Related

add GLKViewController to subview - GLKView context causes crash

I have a pretty simple set up in mind, having a mainViewController that has a GLKViewController on top of it. The idea is having my GLKViewController in a box, that take sup 1/3 of the screen, on the mainViewController. This can be seen below:
That white box is my own custom GLKViewController with the follow code:
boxViewController.h
//boxViewController.h
#import <UIKit/UIKit.h>
#import <GLKit/GLKit.h>
#interface boxViewController : GLKViewController
#end
boxViewController.m
//boxViewController.m
#import "boxViewController.m"
#interface boxViewController () { }
#property (strong, nonatomic) EAGLContext *context;
#end
#implementation boxViewController
-(void)viewDidLoad {
[super viewDidLoad];
self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
if (!self.context) {
NSLog(#"Failed to create ES context");
}
GLKView *view = (GLKView *)self.view;
// view.context = self.context;
view.drawableDepthFormat = GLKViewDrawableDepthFormat24;
}
#end
On my mainViewController in the viewDidLoad I simply call boxViewController like this:
boxViewController* box = [[boxChartViewController alloc] init];
box.view.layer.frame = CGRectMake(10, 50, self.view.frame.size.width-20, self.view.frame.size.height/3);
[self.view addSubview:box.view];
which works perfect.
Notice that in my boxViewController.m I had view.context = self.context commented out. If you uncomment it, my application crashes without any error messaging (it breaks with a EXC_BAD_ACCESS in the objc_msgSend assembly code [line 8 to be specific]).
What am I doing incorrectly that when I set the context the application crashes? From all the tutorials I noticed that they have the same set up, except not setting the controller on another controller. Though I don't understand why GLKViewController couldn't be framed on another controller, so I don't think that's the issue.
After a few hours of messing around I found that adding the viewController as a child works:
#import "mainViewController.h"
#implementation mainViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.view.layer.backgroundColor = [UIColor colorWithRed:242.0f/255.0f green:242.0f/255.0f blue:242.0f/255.0f alpha:1.0].CGColor;
boxViewController* chart = [[boxViewController alloc] init];
chart.view.layer.frame = CGRectMake(10, 50, self.view.frame.size.width-20, self.view.frame.size.height/3);
chart.view.layer.borderColor = [UIColor blackColor].CGColor;
chart.view.layer.borderWidth = 2.0f;
[self addChildViewController:chart];
[self.view addSubview:chart.view];
}

How can I access the property of a parent Class correctly in Objective-C?

I am working at a an Interface for an iPhone App. I recognised that I have repeating objects in my different views so i want to make a parent ViewController. But now when I want to acces a property of this parent ViewController from a child ViewController I get some problems. To be specific I want to have a general ProgressView in each View but I want it to be hidden when the View appears. When I access the ProgressView.hidden from the child ViewController to get it the ProgressView shown i get no errormessage, but at the running programm nothing happens.
My Code looks like this:
ParentViewController.h:
#import <UIKit/UIKit.h>
#interface ParentViewController : UIViewController{
UIProgressView *progressView;
}
#property (readwrite) UIProgressView *progressView;
#end
ParentViewController.m:
- (void)viewDidLoad
{
CGRect progressViewFrame = CGRectMake(0, 407, 320, 9);
progressView = [[UIProgressView alloc] initWithFrame:progressViewFrame];
progressView.hidden = TRUE;
[self.view addSubview:progressView];
[super viewDidLoad];
}
ChildViewController.h:
#import <UIKit/UIKit.h>
#import "ToolbarViewController.h"
#interface ChildViewController : ParentViewController
#end
ChildViewController.m:
- (void)viewDidLoad
{
progressView.hidden = FALSE;
[super viewDidLoad];
}
if i do it like this the ProgressView Bar stays hidden and i get no errors. I also checked whether the viewDidLoad method was called and yes it gets called.
greetings
C4rmel
When you call progressView.hidden = FALSE; before calling [super viewDidLoad];, the progressView has not been initialized yet; it is still nil, so the assignment has no effect.
In the ChildViewController.m, the function calling sequence is incorrect.
Change it by below way:
- (void)viewDidLoad
{
[super viewDidLoad];
progressView.hidden = FALSE;
}
Here call to super will create progressView. Then you can hide it. Otherwise you are hiding a view which is not even created.

(Target: Object) not working for setting UIButton from outside viewController

I can create the UIButton.
But the callback: target: object won't work within the current object. ??
I can only do "self.viewController" object, and can't do "self" for current object.
thx
#import <Foundation/Foundation.h>
#import "ViewController.h"
#class ViewController;
#interface CGuiSetup : NSObject
{
#public
ViewController *viewController;
}
- (void) Setup;
- (void) ButtonRespond:(UIButton*) btn;
#end
#import "CGuiSetup.h"
#implementation CGuiSetup
- (void) ButtonRespond:(UIButton*) btn
{
NSLog(#"ButtonRespond");
}
- (void) Setup
{
UIButton *btn = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[btn setFrame:CGRectMake(10,10,100,100)];
// But I want to call the following in the CGuiSetup object (self) instead... but it crashes out if I leave it as just "self"
[btn addTarget:self.viewController action:#selector(ButtonRespond:)
forControlEvents:UIControlEventTouchUpInside]; //works: for "self.viewController" if I put ButtonRespond in the ViewController
[btn addTarget:self action:#selector(ButtonRespond:)
forControlEvents:UIControlEventTouchUpInside]; //fails: for "self"
[self.viewController.view addSubview:btn];
}
#end
#import <UIKit/UIKit.h>
#import "CGuiSetup.h"
#class CGuiSetup;
#interface ViewController : UIViewController
{
CGuiSetup *guiSetup; //<---- had to take this out of the "viewDidLoad" method
}
#end
- (void)viewDidLoad
{
[super viewDidLoad];
guiSetup = [CGuiSetup alloc];
guiSetup->viewController = self;
UIButton *btn = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[btn setFrame:CGRectMake(10,10,100,100)];
[btn addTarget:guiSetup action:#selector(ButtonRespond:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:btn];
}
If you're using ARC, does any object have a retaining reference to CGuiSetup? I sounds like CGuiSetup is instantiated, creates (or maybe receives from another object) the viewController, adds the button to it and then gives the view controller to another object (perhaps by pushing it on a navController or setting it to be the root controller of the app)? Whatever the case is, CGuiSetup it being dealloc'd and the button is trying to send a message to an object that's already been destroyed. How/where is CGuiSetup created? What object retains a reference to it? That's probably where your problem is.
If you don't understand what retain/release is and/or you don't know what ARC is, you need to read Apple's memory management guide: https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/MemoryMgmt/Articles/MemoryMgmt.html
That's probably because your object is destroyed, while _viewController still has retain count greater than 0 (so it's not destroyed). Balance you're retain/release count.

Add and control UIImageView Subview from another class

I have two classes: MainViewController and PlayerImageController (NSObject)
How would I be able to add the subview of my UIImageView from PlayerImageController to my MainViewController and dictate actions like
- (void)viewDidLoad
{
[super viewDidLoad];
[self.view addSubview:[PlayerImageController addPlayerImage]];
}
- (void)somethingHappened
{
[PlayerImageController changePlayerImage];
}
and have my methods in my PlayerImageController class like
+ (UIImageView *) addPlayerImage
{
heroPlayerImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"hero-still.png"]];
[heroPlayerImageView setFrame:CGRectMake(151, 200, 17, 23)];
return heroPlayerImageView;
}
+ (void) changePlayerImage
{
//change image
}
Thanks!
I think in your case you should use the Delegate pattern.
Declare:
#protocol PlayerImageUpdater
- createPlayerImage;
- changePlayerImage;
#end
Then add:
#interface PlayerImageController <PlayerImageUpdater>
then add to MainViewController ivar and property like:
#property (...) id<PlayerImageUpdater> playerDelegate;
set this delegate like: mainViewController.playerDelegate = playerImageControllerInstance;
and use in code:
[playerDelegate createPlayerImage];
[playerDelegate changePlayerImage];
On one hand, I would not recommend using class methods but instance methods. This way, you could implement as many instances of your class as you need and keep a reference to your instances to update them.
On the other hand, if the UIImageView is the important attribute of your class, I suggest you implement it as a UIView subclass (if it is not, you can do it as an NSObject subclass as well, and get its UIImageView attribute).
Have a look at the following code:
PlayerImageController.h:
#interface PlayerImageController : UIView{
UIImageView *_heroPlayerImageView;
}
-(void) changePlayerImage;
PlayerImageController.m:
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
_heroPlayerImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"hero-still.png"]];
// x = 0 and y = 0 because its relative to its parent view, the object itself.
[_heroPlayerImageView setFrame:CGRectMake(0, 0, frame.size.width, frame.size.height)];
[self addSubview:_heroPlayerImageView];
}
return self;
}
MainViewController.h:
#import "PlayerImageController.h"
#interface MainViewController : UIViewController{
PlayerImageController *_player;
}
MainViewController.m:
- (void)viewDidLoad
{
[super viewDidLoad];
_player = [[PlayerImageController alloc] initWithFrame:CGRect(151, 200, 17, 23)];
[self.view addSubview:_player];
}
- (void)somethingHappened
{
[_player changePlayerImage];
}
I hope it can help you (I haven't actually tried the code above, it could have some syntax errors).
If you are not using ARC, remember to retain and release your variables! Good luck!

Create UIImageView in object

I am trying to create a UIImageView programmatically in a method of a object.
-(void) createApple
{
CGRect myImageRect = CGRectMake(100, 100, 64, 64);
image = [[UIImageView alloc] initWithFrame:myImageRect];
[image setImage:[UIImage imageNamed:#"Apple.png"]];
[self.view addSubview:image];
[image release];
}
when I use this code in the ViewController.m it works fine but when I use it in my Apple object I get an error:request for member 'view' in something not a structure or union.
What do I use instead of self.view???
Sorry if I'm repeating posts. I saw a similar question to this but wasn't sure if it was exactly the same situation.
If I got your question correctly you have an interface like this
#interface Apple: NSObject
#end
If you want to create an UIImageView using NSObject methods try this
//first declare a method of UIImageView type
#interface Apple: NSObject
{
}
-(UIImageView *)ImageView;
#end
now come in your Apple.m file
#implementation Apple
-(UIImageView *)ImageView
{
UIImageView *imgView = [UIImageView alloc]initWithFrame:CGRectMake(100, 100, 64, 64)];
imgView setImage:[UIImage imageNamed:#"Apple.png"];
return imgView;
}
#end
now call this method where you want to show your image, in your ViewController.m file you can call it like this- You just have to import your object class first.
#import "ViewController.h"
#import "Apple.h"
#implementation ViewController
-(Void)ViewDidLoad
{
Apple *appleObject = [Apple alloc]init];
UIImageView *imageViewOfViewController = [appleObject ImageView];
[self.view addSubView:imageViewOfViewController];
}
#end
I show you imageview method in viewDidLoad method you can call it any where like this.
The line causing the error would be:
[self.view addSubview:image];
If your Apple object does not extend UIViewController then there will be so self.view property on it, unless you have defined one yourself.
As to what to use instead, that is difficult to say without knowing specifically what you are trying to do.
However, assuming that you want your Apple class to display something on the screen, then probably what you want to do is update Apple so that it extends either UIViewController or (perhaps) UIView. Then in the first case you can use self.view, and in the second case you can just do [self addSubview: image];.
Because "view" is a property of UIViewController and your "apple object" doesn’t have one.
The problem is, you're not calling this method inside UIViewController. Personally I'd do it slightly other way:
- (UIView*) newAppleView
{
CGRect myImageRect = CGRectMake(100, 100, 64, 64);
UIImageView *imageView;
imageView = [[UIImageView alloc] initWithFrame:myImageRect];
[imageView setImage:[UIImage imageNamed:#"Apple.png"]];
return [imageView autorelease];
}
Assuming the newAppleView method is implemented in your UIViewController, you can easly add new apple:
[self.view addSubview:[self newAppleView]];