I had a TTTableViewController used in iPad and initially I want it to be empty. When it first loads it actually calls:
- (id)initWithNavigatorURL:(NSURL*)URL query:(NSDictionary*)query {
if (self = [super init]) {
self.dataSource = nil;
}
return self;
}
However, the "loading" spinner stays in there and won't go away.
Why is this? I thought that this could happen because init wasn't called, but indeed it is.
I need some help.
When a TTTableViewController is presented on screen, it accesses it's model. If there's no model set, like in your case it creates a model in [TTModelViewController createInterstitialModel]. By default this will be a TTModel (the class not the protocol), which in turn does nothing then appearing to be loading.
In your createModel implementation you would need to create a model that does what you want and assign that to self.model.
Also note, that creating dataSources and / or model in the initializer is not optimal, consider creating your dataSources / models in createModel. They will be created only when needed (when the view appears on screen).
Related
I am working on a project in which I have a model that is used by several different views and thereby viewcontrollers. These viewcontrollers have no knowledge of each others' existence, nor do they have any relationship to each other. This means that I have a model* in each of the viewcontrollers and when the views are loaded, I allocate the model in each class and make the pointers point at it. Or short: I allocate my model n times in n classes that use it, which I think is a waste of memory (not that I will run out of memory, but I think it's bad practice).
Is there a way in iOS where I (while still maintaining good MVC practice) am able to create and use the same instance of my model? Usually I have been programming in c++ where I would pass a reference to the model to the constructor of each class that should know the model. Example (c++):
// Let to classes know of the same model object
MyModel model;
ControllerA myControllerA(&model);
ControllerB myControllerB(&model);
Instead I do the following in each class that use my model (objective-c):
// ControllerA
model = [[MyModel alloc] init];
// Controller B
model = [[MyModel alloc] init];
I don't want to make all models singleton objects and in this specific project I think using an observer pattern would be an overkill.
So, my question is: How can i achieve this and is it even possible?
You can write your own initializer that takes a pointer to the Model.
in the .h file of your ControllerA and B
#property(nonatomic,assign)Model* myModel;
-(id)initWithModel:(Model*)model;
in the .m file of your ControllerA and B
#synthesize myModel;
-(id)initWithModel:(Model*)model{
self = [super init];
if(self){
self.mymodel = model;
}
return self;
}
EDIT
If you are using the IB you would write your initilizer in the following way:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil model:(Model*)model
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
self.mymodel = model;
}
return self;
}
I think, you need to use a singleton pattern
Trivial implementation of witch looks like this
YourClass.h
+ (id)sharedInstance;
YourClass.m:
+(id)singleton {
static dispatch_once_t pred;
static MyClass *shared = nil;
dispatch_once(&pred, ^{
shared = [[MyClass alloc] init];
});
return shared;
}
Good article about it apple reference
You've got the right idea: provide the model to the view controllers that need it when you create them; don't make go looking to a singleton or other global for the information they need.
Since you asked about view controllers, and since those are often instantiated from .xib or storyboard files, you may need to adjust your approach a little. Instead of providing a reference to the model in the initializer, you can simply add a property that stores a reference to the model to each of your view controllers. The object that's responsible for creating a view controller can then provide the model after the controller has been created. For example, your app delegate's -applicationDidFinishLaunchingWithOptions: method will be called after the root view controller is created, and that's a good time to set up the model and point the root view controller at it:
-applicationDidFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// set up the model
self.model = [[MyModel alloc] initWithFile:...];
// get the root controller and give it a pointer to the model
MyFirstViewController *firstController = self.window.rootViewController;
firstController.model = model;
}
The root view controller can then pass the model on to other view controllers that it creates. If you have a tab based app, the app delegate might instead pass the model on to all the view controllers under the tab controller. In that case, your tab controller will be the window's root view controller, and you'd get to your view controllers like this:
NSArray *controllers = self.window.rootViewController.viewControllers;
The big advantage here over the singleton approach is that your view controllers don't assume anything about the rest of the app. They'll each use whatever model you give them. That makes your code cleaner, easier to manage, easier to rearrange, and so on.
Once you create your model objects, you can store these instances in a global dictionary with a key, in some class which you have only one instance of, i.e. a singleton. When a view controller needs this model, it can ask this singleton to give it the data it needs by providing a dictionary key.
Or you can store your data in coredata, and fetch it from there whenever you need it from a view controller. This way you'll also achieve persistence, if you need it.
I am working on an iPhone app. Initially, I had my pickerview in the same screen so this was just a one page app. After skinning it i realized that i want the pickerview on it's own separate page. So i did that. However, my pickerview originally would update uilabels and other objects on that same page. How can I have my pickerview access those objects from it's new view?
- (IBAction)ShowPickerAction:(id)sender {
if (self.theView == nil) {
theView = [containerView initWithNibName:#"containerView" bundle:nil];
theView.parentView = self;
}
self.theView.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self presentModalViewController:self.theView animated:YES];
}
That is the method I am using to call my new view. But line 3 of the above code gives me the error "No known class name for selector initWithNibName:bundle". I think that error is related to something that i did wrong in my header file. My new class is called containerView. So i did this in my header:
#interface ViewController : UIViewController {
containerView *theView;
But that gives me the error "Unknown type name containerView" even though i do have a class named containerView!!
Look into uiappdelegate protocol or try passing values to through a static function to the previous page.
Use a delegate to pass information back and forth to the view object that instantiatrd the picker view. You want to keep your code coupling as loose as possible, especially if you might like to drop it into your next project. Using a delegate and/or blocks are some of the best ways.
In my MainControllerClass I have a few XIB's that I am initializing and throwing on the screen - Here is an Example
self.widgetPeopleToBuyFor = [[WidgetAddPeopleToBuyFor alloc] initWithNibName:#"WidgetAddPeopleToBuyFor" bundle:nil andUser:self.currentUser];
self.widgetPeopleToBuyFor.view.frame = CGRectMake(10,10,492,537);
self.widgetPeopleToBuyFor.delegate = self;
[self.viewMainView addSubview:self.widgetPeopleToBuyFor.view];
[self.viewMainView bringSubviewToFront:self.widgetPeopleToBuyFor.view];
if ([self.currentUser.wPeopleToBuyForRect length] > 2) {
NSLog(#"LOADING FROM DB");
CGRect rect9 = CGRectFromString(self.currentUser.wPeopleToBuyForRect);
self.widgetPeopleToBuyFor.view.frame = rect9;
}
else
{
NSLog(#"FIRST TIME");
self.widgetPeopleToBuyFor.view.frame = CGRectMake(10,10,492,537);
}
I'm building this app so the user can resize the the view to their desire, move it around etc. So when he user Exits I call this method to grab the view information.
- (NSString *) showInfoViewSize: (UIView *) view
{
return NSStringFromCGRect(view.frame);
}
So from there I save the information in the Database, it saves ok - But I just learned the size doesn't matter - I could change it to (10,10,200,200) and the view stays the same exact size on the screen.
So I know I threw a buch at you there. But in the End, the DB works fine - the IF statement gets fired off - Its just I can't resize the XIB on launch.
Should I be resizing it a different way? Should I keep track of the TRANSFORM from the pinch recoginier and just retransform it the scaleFactor at load? Seems kinda hokey to me.
Any help is appreciated - I've been beating myself up on this, and I bet its something super simple I'm missing.
view does not loads from xib until it is presented and hence at that time there will be no frame to set for view. let the view load first then set its frame afterwards.
change the view.frame in "viewWillAppear" in your controller's implementation.
Normally when something is just not working, it's because you've got a nil floating around somewhere you didn't expect. Remember, sending messages to nil is completely legal in ObjC (it just always returns nil).
So I'd step through this in the debugger and check everything to make sure it's a real instance and not nil.
But generally what you're doing (allocing a controller with a nib, setting that controller's view's frame, then adding the controller's view to a superview) is the correct way to do it.
The situation here is, I have an application with a tab bar, and each tab has a TTTableViewController with Datasource and TTURLRequest Models
When I do this series of actions, i get this no data table:
(1) While my fist view is loading, and the items is not yet displayed (Three20 Loading...)
(2) then I switch to another tab
(3) Then go back to that first view, I get an empty table. but when
I tried to breakpoint and look into the visible cells, the model, the datasource, they have values, but how come it displayed an empty table.
So here's a quick fix for my problem, i know this is not the best way.So what I did was to set a private boolean if it needs to invalidate the model, when user moves to another view and while the model is not yet loaded, i set the flag _isNeedtToInvalidateModel=YES
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if(_isNeedToInvalidateModel) {
[self invalidateModel];
_isNeedToInvalidateModel = NO;
}
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
if(![self.model isLoaded]) {
_isNeedToInvalidateModel = YES;
}
}
Somehow it does not show the empty table view anymore
I am trying to change the class of objects created with a nib with the iPhone SDK.
The reason for this is; i dont know until runtime what the class is that i want the nib object to be (though they will have the same UIView based super class), and i dont want to create a different nib for every eventuality - as the .nib will be the same for each, apart from the class of one object.
I have been successful, with a couple of methods, but either have some knock on effects or am unsure of how safe the methods I have used are:
Method 1: Override alloc, on the super class and set a c variable to the class I require:
+ (id) alloc {
if (theClassIWant) {
id object = [theClassIWant allocWithZone:NSDefaultMallocZone()];
theClassIWant = nil;
return object;
}
return [BaseClass allocWithZone:NSDefaultMallocZone()];
}
this works well, and i assume is 'reasonably' safe, though if have a nib with the correct class as the class identity in the Nib, or I alloc a subclass myself (without setting 'theClassIWant') - an object of the base class is created. I also dont really like the idea of overriding alloc...
Method 2: use object_setClass(self,theClassIWant) in initWithCoder (before calling initWithCoder on the super class):
- (id) initWithCoder:(NSCoder *)aDecoder {
if (theClassIWant) {
// the framework doesn't like this:
//[self release];
//self = [theClassIWant alloc];
// whoa now!
object_setClass(self,theClassIWant);
theClassIWant = nil;
return [self initWithCoder:aDecoder];
}
if (self = [super initWithCoder:aDecoder]) {
...
this also works well, but not all the subclasses are necessarily going to be the same size as the super class, so this could be very unsafe! To combat this i tried releasing and re-allocing to the correct type within initWithCoder, but i got the following error from the framework:
"This coder requires that replaced objects be returned from initWithCoder:"
dont quite get what this means! i am replacing an object in initWithCoder...
Any comments on the validity of these methods, or suggestions of improvements or alternatives welcome!
While I'm curious to see if you can pull this off using your approach, you may want to consider using custom placeholder objects.