I work for a long time with MVC but isn't assured that correctly I use this pattern in iOS.
This is my understanding and source code which i use for divisions on model view and controller.
Description:
Model (for example - class MyModel)
Model this is my data. I use model for defined calculation, data acquisition from the Internet and further I notify the controller on changes in model for example through the NSNotificationCenter.
Controller (for example - class MyController)
The controller can directly contact the request of its model data, and go directly to the display in view.
View (for example - class MyView)
View - display and gathering of events from users. View can interaction with controller through target-action and delegate.
Code:
class MyModel:NSObject
.h ... (some header code)
.m
Initialization method...
// method for get data from internet
-(NSData *)my_getDataFromInternet:(NSURL *)url{
NSData *data=[NSData dataWithContentsOfURL:url];
return data;
}
class MyController:UIVIewController
#import "MyView.h"
.h
MyView * my_view;
#import "MyData.h"
.m
Initialization method...
- (void)init{
my_view = [[MyView alloc]init];
my_view.my_target = self;
self.view = my_view;
}
-(void)mycontrolleraction{
MyData * my_data = ...
[my_data my_getDataFromInternet:some_url_image];
my_view.my_image = [UIImage imageWithData:self.my_data];
}
class MyView:UIView
.h
UIImage * my_image;
property(nonatomic, assign)id my_target;
.m
Initialization method...
- (void)initWithFrame{
UIButton * my_button = ...
[button addTarget:my_target ....
my_image = ...
[self addSubview:my_image];
[self addSubview:my_button];
}
I add target to my button - my_target (my_target - this is my MyController). When user tap in my button - method is executed in the MyController and ask data from my MyData class.
I would like to know where my mistake in using this method in the MVC.
It looks like you've got the right idea. I usually think of the model as something that stores the data as well as operating on it, so it seems a little odd to have the model fetch the image and then just return it without storing it. Having the model hold onto the data would let it avoid having to fetch it again later, but the way you have it isn't wrong, and where the data comes from is something that should be entirely up to the model.
One thing I'd suggest, not related to MVC, is to follow the convention for initializers. Your initialization methods must call the superclass's designated initializer, so your controller's -init should look like:
-(id)init
{
if ((self = [super init])) { // double parens to avoid warning about = vs ==
my_view = [[MyView alloc] init]; // assuming my_view is an ivar
my_view my_target = self;
}
return self;
}
The same goes for your view and model classes.
Related
I have this myViewController, that instantiates instances of itself.
Currently, I have a UIButton, that triggers the method
-(void)somethingImportant
However, I want that somethingImportant to happen during the ViewDidLoad, so I don't have to push that button.
But if I put somethingImportant in the ViewDidLoad of myViewController, it is recursively called as many times I have a subview of myViewController.
I tried to put somethingImportant in the application didFinishLaunchingWithOptions: of my app delegate, but somehow that does't work either.
EDIT
So here's the code that might be relevant. I have this UIScrollView with a lot of subviews of myViewController:
- (void)configureScrollView
{
for (int i = 0; i < [self.childViewControllers count]; i++) {
...
myViewController * theSubview =[self.childViewControllers objectAtIndex:i];
....
[theScrollView addSubview:[theSubview view]];
}
}
What is the best approach to make sure that somethingImportant is called only once?
I have this class, that instantiates instances of itself.
This inherently sounds like a bad idea and can easily lead to recursion if you're not careful. Therefore I would suggest you rethink your logic. If you need multiple instances of a class, you should be managing those instances from outside that class, not from within.
However, if you're still insistent on doing this - you can do something similar to what sschale suggests and use a variable to keep track of whether you've called your method or not.
The thing is you'll need to define this variable as static in order for it to be stored at class scope, not instance scope.
For example:
static BOOL firstCalled = NO;
- (void)viewDidLoad {
[super viewDidLoad];
if (!firstCalled) {
firstCalled = YES;
[self foo];
}
}
Each subclass should be calling [super viewDidLoad], on up the chain, so that code really should only be called once.
However, if you need to make sure it executes only once, add #property (nonatomic) BOOL runOnce; to that file's interface, and then in -(viewDidLoad) do:
if(!self.runOnce) {
//all code that is only run once
self.runOnce = YES;
}
In my XCode project, I want some default setup settings, which basically is a set of variables like GlobalTintColor, ServerUrl and so forth.
I then need to override some of these settings per client/target.
These settings are only for interval use, which means I'm not looking for settings bundle type solution.
I don't want to have duplicate settings, so some sort of inheritance seems to be the right way to go.
I was thinking I'd make a parent class carrying all the default settings, and then a subclass for each client, overriding settings as needed. I just can't figure out how I'm going to load these settings. I figured only the clients that needed to override settings had a subclass. Other clients just used the default settings as defined by the parent class.
But when I'm loading the settings at application start, I then need to check if the subclass is available, and if not, I only load the super class.
But then I get the problem of what kind of class the settings are: subclass or superclass?
I've been looking into categories as well as class clustering, but haven't found a solution so far.
Seems to me this is functionality a lot of app developers need. Does any of you know of a good pattern to solve this?
To illustrate:
- (id) getAppConfigurationSettings {
id settings;
if ([AppConfigurationSettings class]) {
settings = [AppConfigurationSettings class];
} else {
settings = [DefaultAppConfigurationSettings class];
}
return settings;
}
Do you want something like this ?
"Parent.h"
#interface Parent : NSObject
#property(nonatomic,strong)UIColor *color;
#end
"Parent.m"
#import "Parent.h"
#implementation Parent
-(void)setColor:(UIColor *)color{
self.color=color;
}
#end
Then you create another class which will inherit Parent say Child
Child.h
#import "Parent.h"
#interface Child : Parent
#end
Child.m
#import "Child.h"
#implementation Child
//Override the actual color
-(void)setColor:(UIColor *)color{
self.color=color;
}
#end
Then you can use it like below
Parent *parent=[[Parent alloc] init];
[parent setColor:[UIColor redColor]];
Child *child=[[Child alloc] init];
[Child setColor:[UIColor blueColor]];
I hope it will give you enough idea..
Updated
For custom initialization you can create some enum, and do your initializations accordingly like below
typedef enum {
kParent = 1,
kChild = 2
}kSettings;
-(void)updateColor:(kSettings)settingType{
id classObj;
switch (settingType) {
case kParent:
classObj=[[Parent alloc] init];
break;
case kChild:
classObj=[[Child alloc] init];
break;
default:
break;
}
[classObj setColor:[UIColor redColor]];
}
Note - The above code is not tested may not be completely correct, but can be like this.
When I hear about "base" and "override", I immediately think of a hierarchy of classes, so #iphonic answer does the job pretty well, although I would design it in a slightly different way:
"BaseSettings"
#interface BaseSettings : NSObject
... properties
#end
#implementation BaseSettings
- (instancetype) init {
self = [super init];
if (self) {
[self constantInit];
[self dynamicInit];
}
}
// Put here initialization that won't be overridden
// in inherited classes
- (void) constantInit {
}
// Put here initialization that will be overridden
// in inherited classes
- (void) dynamicInit {
}
#end
"SettingsInheritor"
#interface SettingsInheritor : BaseSettings
#end
#implementation SettingsInheritor
- (void) dynamicInit {
// Call base method so that not overriden settings
// are still initialized properly
[super dynamicInit];
// Override settings here
...
}
The constantInit method is for convenience only, to let you visually separate constant from overrideable settings - so you can get rid of it if you won't need or like it.
What can be improved in #iphonic's answer is how the actual settings class is instantiated, so I propose a different approach.
As described here, you can use obj_getClassList() to obtain the list of all registered class definitions - then you can loop through all of them, and check if its superclass is BaseSettings (or whatever you want to call the base settings class), using class_getSuperClass() or isSubclassOfClass:. Note: the latter method returns YES if subclass or identical, something to take into account when comparing.
Once you find a class inheriting from BaseSettings, you can break the loop and instantiate the found class (for instance using class_createInstance()). A (untested) skeleton is like this:
int numClasses = objc_getClassList(NULL, 0);
if (numClasses > 0) {
BOOL found = NO;
Class settingsClass;
Class *classes = (__unsafe_unretained Class *)malloc(sizeof(Class) * numClasses);
for (int index = 0; index < numClasses; ++index) {
Class curClass = classes[index];
Class superClass = class_getSuperclass(curClass);
const char *superClassName = class_getName(superClass);
if (strcmp(superClassName, "BaseSettings") == 0) {
settingsClass = curClass;
found = YES;
break;
}
}
if (found) {
// Create the class instance from `settingsClass`
}
free(classes);
}
Credits to Ole Begemann for (most of) the above code
I have 2 classes which names are A and B, I have UIScrollView with pagecontroller in class A and I have a UILabel and NSMutableArray in B.
I used this event for get pagecontroller's page and i am sending number of page to classB for use array's element.
//ClassA
-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
CGFloat pageWidth = self.imageScrollView.frame.size.width;
int page = floor((self.imageScrollView.contentOffset.x - pageWidth / 2) / pageWidth) + 1;
ClassB *obj = [[ClassB alloc]init];
[obj changeDiscount:page];
}
//ClassB
- (void)viewDidLoad
{
numbers = [[NSMutableArray alloc]initWithObjects:#"15",#"25",nil];
}
-(void) changeDiscount:(int)currentPagePresentation{
NSLog(#"currentI = %i",currentPagePresentation);
_discountLabel.text = [NSString stringWithFormat:#"%# Discount",[numbers objectAtIndex:currentPagePresentation]];
}
I can call the changeDiscount method but array is coming null every time and I can't set the string to label.
What am I doing wrong?
Thanks for your answer and advice.
The reason why your _discountLabel.text's string is equal to null is because the numbers array that you are accessing has not even been initialised.
The reason why your numbers array has not been initialised is because the viewDidLoad method only gets called as the method states: WHEN the view has loaded ;)
If you want to access the array after creating an instance of your class, its best to setup the numbers array in an init method or so.
All you've done is:
//This creates a new instance of your second class B.
ClassB *obj = [[ClassB alloc]init];
//Youre trying to access the numbers array when you havent even loaded the view
//All you've done is create an instance of it and then calling a method with an empty numbers array.
[obj changeDiscount:page];
And that doesn't sit well with your existing code. Please continue to read to understand why.
You also definitely don't want to be creating new instances of your class every time your scroll view delegate method is called. I highly suggest you revisit that code and find an appropriate place for that code.
Solution
I suggest you revise the view controllers programmers guide on the apple website before doing anything else.
Follow step 1.
Follow step 1 again.
Then something you can do is:
Method 1: - lazy method In class B you could create an instance method like so:
//.h
-(void)setupArray;
//.m
-(void)setupArray{
numbers = [[NSMutableArray alloc]initWithObjects:#"15",#"25",nil];
}
//Then you can do something like this in class a
ClassB *obj = [[ClassB alloc]init];
[obj setupArray];
[obj changeDiscount:page];
Method 2: more appropriate If you want to do it in one go you can do this, and create an init method.
//.h
//in your Class B .h file you create an instance method like so:
-(void)init;
//.m file
-(id)init{
self=[super init];
if(self)
numbers = [[NSMutableArray alloc]initWithObjects:#"15",#"25",nil];
return self;
}
//Then in your class a method you can do this:
//Like before.
ClassB *obj = [[ClassB alloc]init];
[obj changeDiscount:page];
Ok, there are a few things causing issues here:
You are creating an instance of ClassB within the scope of scrollViewDidScroll of classA.
As soon as that method completes, that new object will be deallocated.
ClassB initialises the numbers array in viewDidLoad. This method will be called only when a UIViewController subclass loads it’s UIView, so ClassB must be a UIViewController subclass and you need to have presented it.
viewDidLoad is called when you first time access view property of that viewController.until then view is nil.
So your numbers array wont be initialised because you are calling changeDiscount method before viewDidLoad is executed.
So, move the initialising from viewDidLoad to init or initWithNib.
-(id)init{
self=[super init];
if(self)
numbers = [[NSMutableArray alloc]initWithObjects:#"15",#"25",nil];
return self;
}
I have an application where A View Controller (A)is called twice in close succession. Now each time it is called, an NSString object is created, and I need this value to be stored in an NSMutableArray that is a public property of ANOTHER View Controller (B).
In A, I create an instance of the second View Controller (B), and using that instance, add the NSString objects into the NSMutableArray which I've created as a public property. Later, when I am inside View Controller B and print the contents of the NSMutableArray property, the array is empty. Why? Here is the code that is inside View Controller A:
-(void)viewDidLoad {
ViewControllerA *aVC = [[ViewControllerA alloc] init];
if (aVC.stringArray == nil) {
aVC.stringArray = [[NSMutableArray alloc] init];
}
[aVC.stringArray addObject:#"hello"];
[aVC.stringArray addObject:#"world"];
for (NSString *wow in aVC.stringArray) {
NSLog(#"The output is: %#", wow);
}
}
Inside my View Controller B class, I have the following code:
- (IBAction)buttonAction:(UIButton *)sender {
NSLog(#"Button selected");
for (NSString *test in self.stringArray) {
NSLog(#"Here are the contents of the array %#", test);
}
}
Now the buttonAction method gets called, as I do see the line Button selected in the system output, but nothing else is printed. Why? One thing I want to ensure is that View Controller A is called twice, which means I would like to see in the output, "Hello World", "Hello World" (i.e. printed twice), and not "Hello World" printed just once.
The other thing I wish to point out is that View Controller B may not be called at all, or it may be called at a later point in time. In any case, whenever View Controller B is called, I would like to have the values inside the array available, and waiting for the user to access. How do I do this?
Your approach is not ideal, potentially leading to a memory cycle, with two objects holding strong pointers to each other.
You can instead achieve your goal in two ways;
Delegate Protocol
This method allows you to set delegates and delegate methods to pass data back and forth between view controllers
in viewControllerA.h
#protocol viewControllerADelegate <NSObject>
- (void)addStringToNSMutableArray:(NSString *)text;
#end
#interface viewControllerA : UIViewController
#property (nonatomic, weak) id <viewControllerADelegate> delegate;
in viewControllerB.m
// create viewControllerA class object
[self.viewControllerA.delegate = self];
- (void)addStringToNSMutableArray:(NSString *)text
{
[self.mutableArray addObject:text];
}
in viewControllerA.m
[self.delegate addStringToNSMutableArray:#"some text"];
Utility Classes
Alternatively you can use a utility class with publicly accessible methods (and temporary data storage). This allows both viewController classes to access a shared data store, also if you use class methods, you don't even need to instantiate the utility class.
in XYZUtilities.h
#import <Foundation/Foundation.h>
#interface XYZUtilities : NSObject
+ (void)addStringToNSMutableArray;
#property (strong, nonatomic) NSMutableArray *array;
#end
in XYZUtilities.m
+ (void)addStringToNSMutableArray
{
NSString *result = #"some text";
[self.array addObject:result];
}
+ (NSArray)getArrayContents
{
return self.array;
}
in viewControllerA.m
NSString *stringFromObject = [XYZUtilities addStringToNSMutableArray];
in viewControllerB.m
self.mutableArray = [[NSMutableArray alloc] initWithArray:[XYZUtilities getArrayContents]];
I'm not sure what kind of a design pattern you are trying to follow but from the looks of it IMHO that's not a very safe one. However, there are many, many ways this could be accomplished.
One thing though, you said that View Controller B may never get allocated and if it is alloc-ed, it will be down the road. So you can't set a value/property on an object that's never been created.
Since you already aren't really following traditional patterns, you could make a static NSMutableArray variable that is declared in the .m of your View Controller B Class and then expose it via class methods.
So it would look like this:
viewControllerB.h
+(void)addStringToPublicArray:(NSString *)string;
viewContrllerB.m
static NSMutableArray *publicStrings = nil;
+(void)addStringToPublicArray:(NSString *)string{
if (publicStrings == nil){
publicStrings = [[NSMutableArray alloc]init];
}
if (string != nil){
[publicStrings addObject:string];
}
}
Then it would be truly public. All instances of view controller B will have access to it. This, of course is not a traditional or recommended way of doing it—I'm sure that you will have many replies pointing that out ;).
Another idea would be to use a singleton class and store the values in there. Then, when or if view controller B is ever created, you can access them from there.
Since switching to storyboards, I load a view controller via
[self performSegueWithIdentifier:#"identifier" sender:self]
This works perfectly. Now, if I want to set any properties on the destination view controllers, I implement the method prepareForSegue:sender: and set what properties I need to set. Everything works as expected without any problems.
Ever since I starting using this approach over the old
MyViewController *vc = ....
vc.prop = #"value";
[self.navigationController pushViewController:vc];
I've felt that passing parameters to the destination view controller is a little hacky, in particular if the value you're trying to set is not just a static value.
Lets say for example, I have a button which fetches some data from a server. When the data returns, it creates a new object, and then presents a new view controller to display this object. To do this, I call performSegueWithIdentifier:sender:, but that's the end of it. My object is now deallocated and no longer exists, and I have no way of passing it to the prepareForSegue:sender: method, unless I store it in an instance variable.
This feels pretty horrible, as the object isn't meant to last longer than this action, and has no relation to anything else in my current view controller.
In this situation, I understand that I could quite simply request the data in the new view controller but it's just an example.
My question is, is there another way of doing this without it feeling so hacky? Can I get this data into the destination view controller without storing it in an instance variable?
I know I could still use the old approach, but I'd like to stick with the storyboard methods if I can.
Well the sender parameter of the performSegueWithIdentifier:sender is the same one received by the prepareForSegue:sender. So if you want to send a variable to your prepareForSegue:sender the sender is your friend. In your case:
SomeViewController.m
-(void)aMethodThatDownloadsSomeDataFromServer {
NSString *exampleData = [self someDataThatIDownloaded];
[self performSegueWithIdentifier:#"yourSegueIdentifier" sender:exampleData];
}
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if(segue.identifier isEqualToString:#"yourSegueIdentifier"]) {
if([sender isKindOfClass:[NSString class]]) { //maybe you want to send different objects
segue.destinationViewController.stringProperty = sender;
}
else {
segue.destinationViewController.objectPorperty = sender;
}
}
}
The accepted solutios is correct but I frequently use another approach when data are shared between more than two segue. I frequently create a singleton class (let's call it APPSession) and I use it as a datamodel, creating and maintaining a session-like structure I can write and read from everywhere in the code.
For complex applications this solution maybe requires too much error prone coding but I've used it succesfully in a lot of different occasions.
APPSession.m
//
// APPSession.m
//
// Created by Luca Adamo on 09/07/12.
// Copyright 2012 ELbuild. All rights reserved.
//
#import "APPSession.h"
#implementation APPSession
#synthesize myProperty;
static APPSession *instance = nil;
// Get the shared instance and create it if necessary.
+ (APPSession *)instance {
if (instance == nil) {
instance = [[super allocWithZone:NULL] init];
}
return instance;
}
// Private init, it will be called once the first time the singleton is created
- (id)init
{
self = [super init];
if (self) {
// Standard init code goes here
}
return self;
}
// This will never be called since the singleton will survive until the app is finished. We keep it for coherence.
-(void)dealloc
{
}
// Avoid new allocations
+ (id)allocWithZone:(NSZone*)zone {
return [self sharedInstance];
}
// Avoid to create multiple copies of the singleton.
- (id)copyWithZone:(NSZone *)zone {
return self;
}
APPSession.h
//
// APPSession.h
//
// Created by Luca Adamo on 09/07/12.
// Copyright 2012 ELbuild. All rights reserved.
//
#import <Foundation/Foundation.h>
#interface APPSession : NSObject{
}
#property(nonatomic,retain) NSString* myProperty;
+ (id)sharedInstance;
#end
How to read and write the property myProperty from every part of the app code.
// How to write "MyValue" to myProperty NSString *
[APPSession instance] setMyProperty:#"myValue"]
// How to read myProperty
NSString * myVCNewProperty = [[APPSession instance] myProperty];
With this mechanism I can safely write for instance a value in the APPSession in the first ViewController, perform a segue to a second one, perform another segue to a third one and use the variable written during the first segue.
It's more or less like a SessionScoped JavaBean in Java EE. Please feel free to point out problems in this approach.
All of these answers are correct, but I've found a pretty cool way of doing this. I've tested only in iOS 7 and iOS 8
After declaring and setting the value of the object you wish to pass, in the prepareForSegue method,
[segue.destinationViewController setValue:event forKey:#"property"];
//write your property name instead of "property