I've got a first UISplitViewController in wich there is (and it's default) two other view Controller as children and for one of them there is another view controller as child (again it's default).
My problem is that the Model, which is basically a class, used by the business logic is created in the AppDelegate and i'd like to use it in every controller.
I tried to use the viewDidLoad method to pass the model through all the controller but this method is called in the last child and then go through the hierarchical tree to the SplitViewController.
Two constraints i'd like to fullfill are:
I don't want to use a singleton
I don't want all my controllers to know the AppDelegate
Is there a way to to this?
If you don't want to use singleton for any reason you have you can just pass model through those ViewControllers. Just create custom initWithModel: method for each of ViewControllers you have when you create them and hierarchically pass data to them. Maybe create a base class for all of them to keep it common.
For storyboard you can assign model in prepareForSeque method, just after creating ViewControllers.
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if([[segue identifier] isEqualToString:#"myViewController"])
{
[segue.destinationViewController setModel:self.model];
}
}
That could be an answer :
in the .h :
+(MyModel *)shared;
in the .m :
static MyModel *myModel;
#implementation MyModel
+(MyModel *) shared{
if (nil != myModel) {
return myModel;
}
static dispatch_once_t pred;
dispatch_once(&pred, ^{
myModel = [[MyModel alloc] init];
});
return myModel;
}
In this way, you can access to your model anywhere of your app.
Hope that will help!
EDIT: add of dispatch_once
Related
SettingsStore.h
#interface SettingsStore : IASKAbstractSettingsStore
{
#public
NSDictionary *dict;
NSDictionary *changedDict;
}
- (void)removeAccount;
#end
menuView.m
-(IBAction)onSignOutClick:(id)sender
{
SettingsStore *foo = [[SettingsStore alloc]init];
[foo removeAccount];
[self.navigationController pushViewController:foo animated:YES];
exit(0);
}
I want to call this removeAccount function from menuView.m. But I am getting error.
How to fix it and call this removeAccount.
There are few mistakes in your Code please find them below.
[foo removeAccount]; Calling this method is correct
[self.navigationController pushViewController:foo animated:YES];
Not correct because SettingsStore is not subclass of
UIViewController only subclass of UIViewController can be pushed to
Navigation controller
exit(0); Calling this method is not
recommended by Apple
You are calling removeAccount correctly from your menuView.m file, but there are several issues with your code:
You are treating foo as though it were a UIViewController, and it's actually a member of the SettingStore class. Does the SettingStore class refer to an actual screen, or is it more a data object (for storing settings?). If it's the latter, you don't want to push it on. You can create it, and use it, but the user doesn't need to see it.
You are calling exit(0); you can remove that line. If you want to remove the menuView.m file from your memory, remove references to it (e.g. from its parent view controller).
The menuView.m file is confusing, as in, is it a view or a viewController. An IBAction I would normally stick in a ViewController file, rather than a view file. Your basic design pattern is MVC (Model / View / Controller). In this case, it seems your SettingStore file is a Model (data), the menuView.m is a View and your code is for the Controller bit.
I have a view controller whose UI is the identical between 2 classes, but the functionality is different. One of the classes uses the view controller to add a contact, the other uses it to edit a contact.
Is there a way to "reuse" the layout/view of the view controller while having different classes (add/edit class)?
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqual:#"AddContact"]) {
UINavigationController *navigationController = segue.destinationViewController;
AddContact *addContact = (AddContact *)navigationController.viewControllers.firstObject;
addContact.delegate = self;
}
else if ([segue.identifier isEqual:#"EditContact"]) {
EditContact *editContact = (EditContact *)segue.destinationViewController;
editContact.currentContact = [self.contacts objectAtIndex:[[self.tableView indexPathForSelectedRow] row]];
}
}
The segue.destinationViewController is of type ViewContact which both AddContact and EditContact both inherit from. All it does it hold onto the outlets for the textfields that both of its children use.
Unfortunately, the snippet above doesn't work because you can't really typecast parents to their children.
What I normally do is create a single view controller, with a xib included, and add a property like so:
header file
typedef NS_ENUM(NSUInteger, CRUD) { //Create, Read, Update, Delete
CTCreate,
CTRead
};
#property ( assign, readonly ) CRUD option;
And in the initialization of this view controller you'd have something like:
header
- (id)initWithOption:(CRUD)optionValue;
implentation
- (id)initWithOption:(CRUD)optionValue {
...
option = optionValue;
return self;
}
And in the implementation of this class you'd have if statements where the differences are, like when the user hits saves, should this class insert a new record, add, or update a recorded, edit
Hope this helps :) feel free to ask for more clarification :)p
I have created one custom ViewController named SetValueVC with UITextView in it. During creating new product in my app I display UITableViewController with cell that need to be specify. If user click on some cell I display my UIViewController as detailView to allow setting parent UITableViewCells detailLabel.text value. I know how to pass data from child view by using delegate but how it works when there is for example 10 rows? Should I create 10 delegates methods for every row or just create object of SetValueVC for every row in didselectForRow method and use just one delegate method ? Thanks in advance
That could be an answer : Work with a singleton and use your model in your ViewController and your UITableViewController
in the .h :
+(MyModel *)shared;
in the .m :
static MyModel *myModel;
#implementation MyModel
+(MyModel *) shared{
if (nil != myModel) {
return myModel;
}
static dispatch_once_t pred;
dispatch_once(&pred, ^{
myModel = [[MyModel alloc] init];
});
return myModel;
}
In this way, you can access to your model anywhere of your app.
Hope that will help.
I am currently going through the iTunes U Stanford iOS dev. course and I am trying to utilize segues.
In my prepareForSegue method I am trying to update the data on the transitioning VC and this is my code:
-(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if([segue.identifier isEqualToString:#"changeToScreen2"])
{
if([segue.destinationViewController isKindOfClass:[Screen2ViewController class]])
{
"Code to be implemented"
}
}
}
But my Screen2ViewController isn't recognized. Is it safe and proper coding technique to import a view controller to another view controller for segueing purposes or is there another method I should implement?
-------------------------------------------------------------------------------------
I have a new problem now
When I set the values of a UILabel and UITextView with the aforementioned prepareForSegue method and change to Screen2ViewController the labels and text views have not be updated with the values that I have added.
Screen2ViewController *S2VC = (Screen2ViewController *)segue.destinationViewController;
S2VC.myLabel.text = #"Screen 2 is now being viewed";
S2VC.uneditableText.text = #"Why aren't you showing up when I push you";
But these values don't get updated.
Yes it is safe to import view controllers. There are a few caveats however,
Do not import 2 headers into each other, this will cause non-obvious error.
Screen1ViewController.h
#import "Screen2ViewController.h"
Screen2ViewController.h
#import "Screen1ViewController.h"
Import in the .m file instead
Screen1ViewController.h
#import "Screen2ViewController.h"
Screen2ViewController.h
//No imports
Screen2ViewController.m
#import "Screen1ViewController.h"
As a general rule I try to put all the imports in the .m file: both for encapsulation and the above reason. You can also foreword declare a class if you need to use both classes in both header files.
About your new problem: you can only update instances from another view controller if they're made public (in other words, they're declared in its header file). So, with the provided code, you'd need to make myLabel and uneditableText public. However, during prepareForSegue: execution they were not yet allocated. As all you need from those objects is editing their text, it would be better to define two NSString's in the second view controller and then, inside that VC's implementation, you assign them to the objects. Example:
First View Controller
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if([segue.identifier isEqualToString:#"changeToScreen2"])
{
if([segue.destinationViewController isKindOfClass:[Screen2ViewController class]])
{
Screen2ViewController *S2VC = (Screen2ViewController *)segue.destinationViewController;
S2VC.labelText = #"Screen 2 is now being viewed";
S2VC.textViewText = #"Why aren't you showing up when I push you";
}
}
}
Second View Controller's Header
...
#property (nonatomic, strong) NSString *labelText;
#property (nonatomic, strong) NSString *textViewText;
...
Second View Controller's Implementation File
...
- (void)viewDidLoad
{
[super viewDidLoad];
self.myLabel.text = self.labelText;
self.uneditableText.text = self.textViewText;
}
...
Needless to say you must have previously used the Interface Builder to add myLabel and uneditableText as #property's of your Second View Controller.
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