Adding a TTTableViewController (from three20) to another UIViewController - iphone

I'm having a problem with three20 that I'm hopeful someone will be able to help me out with.
I have a TTTableViewController that I use very similarly to how the TTTwitter app in three20's sample projects uses it. I have just three methods that I'm implementing: (id)init, (void)createModel, and (id<UITableViewDelegate>)createDelegate. And I've subclassed TTListDataSource and TTURLRequestModel for my data. To summarize, it's a fairly simple use of TTTableViewController, I'm not doing anything out of the ordinary.
When I add this TTTableViewController to a descendant of UIView, it works just fine. It loads and displays the data perfectly. For example, the following two work fine:
FooTableViewController *controller = [[FooTableViewController alloc] init];
controller.view.frame = CGRectMake(288, 20, 480, 1004);
[self.window addSubview:controller.view];
As does this:
FooTableViewController *controller = [[FooTableViewController alloc] init];
controller.view.frame = CGRectMake(288, 20, 480, 1004);
UIScrollView *scrollView = [[UIScrollView alloc] init];
scrollView.frame = CGRectMake(0, 0, 768, 1024);
// a bunch of scrollView variable initializations..
[scrollView addSubview:controller.view];
[self.window addSubview:scrollView];
The problem arises when I try to add FooTableViewController (which, again, is a subclass of TTTableViewController) to the view of a descendant of UIViewController. The following, for example, doesn't work:
FooTableViewController *controller = [[FooTableViewController alloc] init];
controller.view.frame = CGRectMake(288, 20, 480, 1004);
UIViewController *viewController = [[UIViewController alloc] init];
viewController.view.frame = CGRectMake(0, 20, 768, 1004);
[viewController.view addSubview:controller.view];
[self.window addSubview:viewController.view];
Any idea why this happens? Like I said, it works just fine when I add it to a UIView (that is, one that's not in a UIViewController), but doesn't when I try to add it to a UIViewController. Besides trying to add it like I did above, I also tried subclassing UIViewController and adding it from within. No luck.
Thanks!
EDIT I should be a little more clear on what I mean by "it doesn't work." All I see is an empty table -- just empty rows. Putting a breakpoint on createModel indeed shows that that method is not being called. Thanks!
UPDATE diwup totally pointed me towards the right solution. He was right in that it is because TTTableViewController's viewWillAppear: and viewDidAppear: weren't getting called. I verified this by subclassing UIViewController (I called it FooViewController) and adding the TTTableViewController to its view from within the class (I called the TTTableViewController variable controller). I also overrode two methods like this:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[controller viewWillAppear:animated];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[controller viewDidAppear:animated];
}
And, lo and behold, it worked! Of course, this isn't a long-term solution, I still want to figure out how to get viewWillAppear: and viewDidAppear: called by itself.
UPDATE2 I decided to just override viewDidLoad: like so:
- (void)viewDidLoad {
[super viewDidLoad];
[super viewWillAppear:NO];
[super viewDidAppear:NO];
}
I'm not too pleased with it, it seems hacky (and I should probably make sure viewWillAppear: and viewDidAppear: don't get called twice), but I'm ready to call it a day with it.

My answer may not be relevant. But I suspect that the problem may have something to do with the addSubview: method. Since addSubview: doesn't guarantee the view is on the top level of the view hierarchy, certain methods of that view may not get fired during or after addSubview:. These methods include viewWillAppear:, viewDidAppear: etc etc.
Therefore, if Three20's code relies on those interface methods, things could go wrong. I would suggest you try to present that controller modally and see what will happen.

The same question.
Just call the instace of TTTableViewController ;
for example:
TTTableViewController tableViewController;
[tableViewController createModel]
and
[tableViewController updateView]
in viewDidLoad.
it's works.

Related

removeFromSuperview causes my app to crash

I'm sure this is some stupid mistake, but i'm trying for the past hour to remove a subview from my superview without any success.
On my first view i'm having
UIViewController *helpView = [[[UIViewController alloc] initWithNibName:#"HelpView" bundle:nil] autorelease];
[self.view addSubview:helpView.view];
And then inside helpView i have a button which is connected to an IBAction called "closeHelp" which just does the following:
- (IBAction) closeHelp{
[self.view removeFromSuperview];
}
But this causes my app to crash with EXC_BAS_ACCESS for some weird reason, even those this is inside the HelpView, meaning self.view should be pointed to the correct subview..
Would appreciate your help
Thank you.
Shai.
As Andreas answered, you are trying to remove self.view from its super/parent view.
You basically need to remove the helpView from its parent view.
so it should be
- (IBAction) closeHelp{
[helpView removeFromSuperview];
}
But we dont know what is "helpView" in the above method. As we dont have any handle for it.
So our code should finally look like this.
#define HELP_VIEW_TAG 101 // Give tag of your choice
HelpView *helpView = [[HelpView alloc] initWithNibName:#"HelpView" bundle:nil];
helpView.view.tag = HELP_VIEW_TAG;
[self.view addSubview:helpView.view];
[helpView release];
- (IBAction) closeHelp{
UIView *helpView = [self.view viewWithTag:HELP_VIEW_TAG];
[helpView removeFromSuperview];
}
The self.view does not point to your subview but the root view which your uiviewcontroller manages. You should probably remove only the last object in the subview stack, not the whole view, because now you are removing the whole help view.
Anyway, why do you not present the viewcontroller modally instead of doing this?
[self presentModalViewController:helpView animated:NO/YES];
helpView. modalTransitionStyle = //One of the constants below
UIModalTransitionStyleCoverVertical
UIModalTransitionStyleFlipHorizontal
UIModalTransitionStyleCrossDissolve
UIModalTransitionStylePartialCurl
Usually I am writing self.modalTransitionStyle = // One of the constants
in the viewcontroller which will be presented modally, instead of spreading the code.
You are initializing helpView as a UIViewController.
Make sure you have #import "HelpView.h" (or whatever the helpView .h file is called) in the .h file of the view controller where you are initializing it.
Then, use this code:
HelpView *helpView = [[HelpView alloc] initWithNibName:#"HelpView" bundle:nil];
[self.view addSubview:helpView.view];
That should fix it.
The easiest solution for me eventually was to just define my XIB's file owner as the same class as the parent controller, meaning the parent controller would control both the parent and the subview, which just makes a lot easier. :)
Declare the help view on calss level.
in.h file
#class HelpView;
..
#interface
{
HelpView *helpView;
}
#property(nonatomic,retain)HelpView* helpView;
In.m file
#import "HelpView"
#synthensize helpView;
now add this Code where you want
helpView = [[HelpView alloc] initWithNibName:#"HelpView" bundle:nil];
helpView.view.tag = HELP_VIEW_TAG;
[self.view addSubview:helpView.view];
- (IBAction) closeHelp{
//UIView *helpView = [self.view viewWithTag:HELP_VIEW_TAG];
[helpView removeFromSuperview];
}
-(void)dealloc
{
[helpView release];
}

transitionFromView:toView:duration:options:completion view memory management?

It doesn't seem like
transitionFromView:toView:duration:options:completion:
Handles memory like it indicates in the Apple docs. It is stated that the fromView is removed from the superview (implying a release) and the toView is added to the superview (implying a retain). Is this correct?
When I transition a view, later in my app when the view is actually presented I will get a BAD_ACCESS as the view was deallocated.
Any ideas? Thanks!
UPDATE:
Here is the code where the problem exists:
UIViewController *container = [[UIViewController alloc] init];
container.view.bounds = [UIScreen mainScreen].bounds;
[container.view setBackgroundColor:[UIColor blackColor]];
/* Deallocated in the finish callback */
tutorialViewController = [[TutorialViewController alloc]
initWithNibName:#"TutorialViewController"
bundle:nil];
tutorialViewController.tutorialDelegate = self;
[tutorialViewController loadTutorialData:data];
UINavigationController *nc = [[UINavigationController alloc]
initWithRootViewController:tutorialViewController];
nc.navigationBar.barStyle = UIBarStyleBlackOpaque;
[UIView transitionFromView:[[window subviews] objectAtIndex:0]
toView:container.view
duration:kAnimationDuration
options:UIViewAnimationOptionTransitionCurlUp
completion:nil];
[container presentModalViewController:nc animated:NO];
[container release];
[nc release];
If I do a [tutorialViewController release] at the bottom of this method, I will get the BAD_ACCESS. So it seems like the UINavigationController does not retain the root view controller.
P.S. The tutorialViewController was not a member variable previously, but I have now fixed this problem by simply releasing it after the view has been dismissed.
You should check and confirm that you are not releasing the view yourself, hereby over-releasing. Or perhaps check the dealloc method for the view class which is being released to see if you're over-releasing anything in it.
The answer, after much testing, is simply that UINavigationViewController does not retain the view with initWithRootViewController. The Apple docs aren't clear on this.

viewDidLoad not called when using loadView

Can anyone explain why viewDidLoad does not get called when loadView is used? It's my understanding that viewDidLoad should still get called.
- (void)loadView
{
CGRect currentFrame = [[UIScreen mainScreen] applicationFrame];
UIView* myView = [[UIView alloc] initWithFrame:CGRectMake(currentFrame.origin.x, currentFrame.origin.y, currentFrame.size.width, currentFrame.size.height)];
myView.backgroundColor = [UIColor redColor];
self.view = myView;
[myView release];
[super loadView];
}
- (void)viewDidLoad {
//this never happens
NSLog(#"VIEW DID LOAD!");
[super viewDidLoad];
}
I've just found out that viewDidLoad won't be called if you call loadView manually in your application.
If you call loadView manually you have to call viewDidLoad manually as well.
More over according to apple docs you shouldn't call [super loadView] as it will overwrite your view with a default UIView.
You must have a warning here:
NSLog("VIEW DID LOAD!");
Instead, you should write like this (the # sign is necessary):
NSLog(#"VIEW DID LOAD!");
viewDidLoad will not get called when you create instance of ViewController. When you are pushing it to navigation controller or present it as model viewcontroller, then only the viewDidLoad get called. Until and unless you are presenting viewController, these delegate will not get called. And one more thing, if your viewcontroller presentation over, and still it remains in the stack or memory, then viewDidLoad method will not get called again because its already load the view. Then viewWillAppear and viewDidAppear delegates only get called when you present the same viewController.

global ADBannerView in iPhone app

Is it possible with a standard UINavigationController-rooted app, to have a single ADBannerView visible at the bottom of the screen, below the view hierarchy? That is, without modifying each view-controller/view that can be pushed to the root UINavigationController, can I have a global ADBannerView be visible?
I'm not sure how to set this up, either in IB or in code. Help?
I see similar questions with vague answers. I'm looking for a concrete example.
EDIT: The better way to do this in iOS5+ is likely to use view controller containment. That is, make a root controller that contains your ad and application controller (nav, tab, etc.).
I figured out a way to do this. Here is what I did:
For my first attempt I created a new view controller called AdBannerController. For its view I created a full-screen view and two subviews. The first subview (contentView) is for regular content, the second is the AdBannerView. I used an instance of this view controller as the view controller associated with the app window ( [window addSubview: adBannerController.view] ). Then I added my UINavigationController.view as a subview of adBannerController.view: [adBannerController.contentView addSubview: navigationController.view].
This mostly worked except that viewcontrollers pushed to the UINavigationController never got their will/did-load/unload methods called. Shucks. I read in a few places that this is a symptom of the UINavigationController view not being a direct descendant of the app window.
For my second attempt I took the same AdBannerController and derived it from UINavigationController. This time, I did the following in loadView:
- (void)loadView
{
[super loadView];
_contentView = [self.view retain];
self.view = [[[UIView alloc] initWithFrame: _contentView.frame] autorelease];
[self.view addSubview: _contentView];
_adView = [[ADBannerView alloc] initWithFrame: CGRectMake(0, _contentView.bounds.size.height, 320, 50)];
_adView.currentContentSizeIdentifier = ADBannerContentSizeIdentifier320x50;
_adView.delegate = self;
[self.view addSubview: _adView];
/* for visual debugging of view layout
[[_mainView layer] setCornerRadius: 6.0];
[[_mainView layer] setMasksToBounds: YES];
[[_mainView layer] setBorderWidth: 1.5];
[[_mainView layer] setBorderColor: [[UIColor grayColor] CGColor]];
*/
}
Notice what happens - I let the superclass UINavigationController construct its regular "content" view, but I swap it out and replace it with my own view which is a container for both the content and ad views.
This works pretty well. I'm also using three20 and there were a few things required to make this work with that setup, but not too bad.
I hope this helps someone!
In Apple's dev sample code the iAdSuite project contents projects that have this done for you. Highly recommended.
In my root view controller (w/ ADBannerViewDelegate) I setup my banner by adding it to the nav controller view, which keeps it on top at all times:
banner = [[ADBannerView alloc] init];
banner.delegate = self;
banner.frame = CGRectMake(0.0, 430.0, banner.frame.size.width, banner.frame.size.height);
[self.navigationController.view addSubview:banner];
Note you will have to comment out layoutAnimated in delegate method bannerViewDidLoadAd as it will try to move the ad view up:
- (void)bannerViewDidLoadAd:(ADBannerView *)banner
{
//[self layoutAnimated:YES];
}
I adapted the approach suggested in the iAdSuite given here
http://developer.apple.com/library/ios/#samplecode/iAdSuite/Introduction/Intro.html
I downloaded the code and focused on the 'tab' example. I copied over the BannerViewController.h/.m as is into my project.
I created all my views in the usual way with the storyboard approach. However, in my AppDelegate class I then accessed the already built tab bar - containing all the storyboard built viewControllers.
The AppDelegate class implements the TabBarControllerDelegate protocol:
#interfaceAppDelegate : UIResponder <UITabBarControllerDelegate, UIApplicationDelegate>
The AppDelegate implementation didFinishLaunchingWithOptions method grabs the pre-built tabBar, setting its delegate to self (e.g. the AppDelegate class).
-(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:
// ----------------------------------------------------------
// Set the TabBarController delegate to be 'self'
// ----------------------------------------------------------
_tabBarController = (UITabBarController*)self.window.rootViewController;
// tabController.selectedIndex = [defaults integerForKey:kOptionLastTabSelectedKey];
_tabBarController.delegate = self;
// update tab bar per iAdSuite approach
[self updateiAd];
I then built a new set of controllers per the iAdSuite approach and reset the tab bar with these new tab bar items.
-(void)updateiAd {
NSArray* viewControllers = [_tabBarController viewControllers];
NSMutableArray*newViewControllers = [[NSMutableArray alloc] init];
BannerViewController*bvc=NULL;
for(UIViewController * vc in viewControllers) {
bvc = [[BannerViewController alloc] initWithContentViewController:vc];
[newViewControllers addObject:bvc];
}
// set the new view controllers, replacing the original set
[_tabBarController setViewControllers:newViewControllers];
}
This approach puts the same 'ad' at the bottom of each view, exactly as needed. I also had to set the view title in the viewDidLoad method of each custom viewController (somehow, setting it on the bar item didn't seem to work not did setting the image; the later may reflect an issue with my images however).
My original configuration was
TabViewController
NavController1 NavController2 NavController3 ...
| | |
CustomViewController1 CustomViewController2 CustomViewController3
My final configuration is now
TabViewController
NavController1 NavController2 NavController3 ...
| | |
iAdView1 iAdView2 iAdView3
| | |
CustomViewController1 CustomViewController2 CustomViewController3
In terms of view lifecycle, I should add that only the NavControllers are in existence at the time the updateiAd method is called.
The individual CustomViewControllers1/2/3/etc get created after the call completes.

UIViewController: set self.view to my view or add my view as subview?

I have a question about UIViewController's subview, I created a UIView subclass MainView, which has the exact size of the screen, I wonder which is a better way of adding MainView, consider the following factors:
1 As MainView has same size as the whole screen, the MainView itself may have subviews, but there is no views at the save level as MainView(ie I don't need to add other subviews to self.view).
2 If I use self.view = mainView, do I put the code in loadView(as the viewDidLoad method means the view(self.view) is already loaded)? I see the loadView method is commented out by default, if I add the code to this method, what other code do I need to put together(e.g. initialize other aspects of the application)?
3 If I add mainView via [self addSubview:mainView], are there actually two off screen buffer? One for self.view, one for mainView, both has same size as the screen and one is layered on top of the other(so it wastes memory)?
Thanks a lot!
I'm not sure I completely understand what you're asking, but I'll try to answer a few of the questions you have.
First of all, if you have multiple UIViews on the screen they are all loaded into memory. You have to do -removeFromSuperview and release them to get the memory back.
You can assign your UIView as the UIViewController's view. For example:
MainView *mainView = [[MainView alloc] initWithFrame:CGRectMake(320.0, 480.0)];
self.view = mainView;
[mainView release]; //since the .view property is a retained property
in that case, you have have the view's initialization code in the -init method. Just redefine it like:
- (id)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
//initializations
}
return self;
}
You must implement loadView if you did initialize your view controller with a NIB.
UIViewController takes care of sizing its "main" view appropriately. This is all you need to do:
- (void)loadView
{
UIView* mainView = [[[UIView alloc] initWithFrame:CGRectZero] autorelease];
self.view = mainView;
}
I'd solve all of this by doing it in a xib! If you create a UIView in your xib, you can then change it's class (when you select the UIView there should be a text field in the Class Identity section of the Identity inspector* - type 'MainView' here!)
Then, create your view controller by calling
myViewController = [[MainViewController alloc] initWithNibName:#"MyNibName" bundle:nil];
That should solve your problems; it's the main subview of your view controller (directly accessable from self.view) and you don't need to worry about memory usage, there's only one view :)
Sam
NB * Click tools -> Identity Inspector. I didn't know it was called this until I had to write this answer!
Yes, the first code-snippet shown above is the "standard" approach, AFAIK, when not using (evil!) NIB files -- i.e. when alloc'ing your view in-code, via loadView.
Note it seems one can also get away with the following, instead of hard-coding the screen-rect size:
UIView *myView = [[UIView alloc] initWithFrame:[UIScreen mainScreen].applicationFrame];
self.view = myView;
[myView release];
Note you definitely want to do the [myView release] call since, indeed, as pointed out above, self.view (for UIView) is a retained property.
Cheers, -dk
Perhaps the most important thing to do is make sure you have the following:
self.view.userInteractionEnabled = YES;
While it might not be required all of the time, it fixes the issue where self.view is unresponsive. This issue pops up occasionally.