I have some initialisation code in the viewDidLoad and viewWillAppear: methods that is used across a number of my UIViewController subclasses (which implement < UITableViewDataSource, UITableViewDelegate>):
-(void)viewDidLoad {
[super viewDidLoad];
self.tableView = [[UITableView alloc] initWithFrame:CGRectZero
style:UITableViewStylePlain];
self.tableView.delegate = self;
self.tableView.dataSource = self;
self.tableView.separatorStyle = UITableViewCellSeparatorStyleSingleLine;
[self.view addSubview:self.tableView];
[self.tableView reloadData];
}
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.tableView.frame = self.view.bounds
}
My thinking is to subclass UIViewController and include these methods in the subclass so as not to have to write this code in all my view controllers.
What are your thoughts on this? I'm wondering if this might give rise to issues retaining the tableView.
Cheers
Have you considered this structure:
UIViewController -> your_First_Level_Subclass_View_Controller -> your_Second_Level_Subclass_View_Controller
In the 1st level, you implement those reusable/tableView-related codes, but you don't actually use this 1st level controller. You then subclass this 1st level to create your 2nd level controllers, which are the ultimate controller that you are gonna use.
Related
when set self.view.tag is 10 in loadView ,but in viewDidload it's tag is 0
why ? thanks
- (void)loadView
{
[super loadView];
NSLog(#"loadView %d",self.view.tag);
[self.view setTag:10];
self.view = [[UIView alloc] initWithFrame:self.view.frame];
self.view.backgroundColor = [UIColor yellowColor];
}
#pragma mark view loaded
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(#"ViewDidload %d",self.view.tag);
[self.view setTag:10];
}
The loadView method is not usually implemented. The system calls it in order to create your view hierarchy. At the time it's called, your views will not exist.
Why are you implementing loadView? You probably should not be.
To quote the docs:
If you use Interface Builder to create your views and initialize the
view controller, you must not override this method.
What code are you putting in your loadView method?
Using SDK 6.1, Xcode 4.6.1, I make a new project Master-Detail iOS App, ARC, no storyboards.
Then in the DetailViewController, in the viewDidLoad I add two UITableViews contained in UIViewControllers and make sure the second one is hidden like this:
- (void)viewDidLoad
{
[super viewDidLoad];
UIViewController *lViewController1 = [[UIViewController alloc] init];
UITableView *lTableView1 = [[UITableView alloc] initWithFrame: self.view.frame];
lTableView1.scrollsToTop = YES;
[lViewController1.view addSubview: lTableView1];
lTableView1.dataSource = self;
[self.view addSubview: lViewController1.view];
[self addChildViewController: lViewController1];
UIViewController *lViewController2 = [[UIViewController alloc] init];
UITableView *lTableView2 = [[UITableView alloc] initWithFrame: self.view.frame];
lTableView2.scrollsToTop = YES;
[lViewController2.view addSubview: lTableView2];
lTableView2.dataSource = self;
[self.view addSubview: lViewController2.view];
[self addChildViewController: lViewController2];
// now hide the view in view controller 2
lViewController2.view.hidden = YES;
}
(I make sure the DetailViewController is a datasource that returns 100 rows of UITableViewCells with the textLabel.text set to #"hello")
The presence of the second view controller makes that scrollsToTop (tapping on the status bar) does not work anymore. If I do not use UIViewController containment and just add two UITableViews and set the second one to be hidden, scrollsToTop does work.
What am I doing wrong?
scrollsToTop only works on a single visible view. From the documentation:
This gesture works on a single visible scroll view; if there are multiple scroll views (for example, a date picker) with this property set, or if the delegate returns NO in scrollViewShouldScrollToTop:, UIScrollView ignores the request. After the scroll view scrolls to the top of the content view, it sends the delegate a scrollViewDidScrollToTop: message.
You could try calling [tableView setContentOffset:CGPointZero animated:YES] on each of your table (or scroll) views manually instead. To do this, implement the scrollViewShouldScrollToTop: method in the UIScrollViewDelegate protocol:
- (BOOL)scrollViewShouldScrollToTop:(UIScrollView *)scrollView {
[lTableView1 setContentOffset:CGPointZero animated:YES];
[lTableView2 setContentOffset:CGPointZero animated:YES];
return NO;
}
You can only set 1 ScrollView per ViewController with property .scrollsToTop = YES.
If you set 2 scrollview.scrollsTopTop = YES, it will simply stop functioning.
ie: your sample project (DetailViewController.m) update following lines,
line48: lTableView1.scrollsToTop = YES;
line56: lTableView2.scrollsToTop = NO;
then, scrollsToTop works correctly. If there are more than 1 scrollview you wish to concurrently setScrollsToTop, keep digging around. good luck!
I am currently experimenting with your project. When
lViewController2.view.hidden = YES;
is replaced with
lTableView2.hidden = YES;
then the scrolling works, even with controller containment.
I tried to insert a view between the controller's view and the table and then hide this view, but the table was not scrolling.
I tried to hide the controller by experimenting with shouldAutomaticallyForwardAppearanceMethods but the table was not scrolling.
Result: From my experiments, only one scroll view must be visible in the view hierarchy and the hidden property of the parent views is not checked out. hidden must be set to NO on all other scroll views, not their parent views.
After testing several options and various hits and try I finally settled to one final solution, i.e. setBounds: of scrollView (that is tableView in your case) and it works good. You'll have to put extra effort for animation although.
CGRect frame = scrollView.frame;
frame.origin.x = 0;
frame.origin.y = 0;
[scrollView setBounds:frame];
By the way in your case, try returning YES to
- (BOOL)scrollViewShouldScrollToTop:(UIScrollView *)scrollView;
Although if not defined, assumes YES.
I have used this and now it works fine.
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
UIViewController *lViewController1 = [[UIViewController alloc] init];
UITableView *lTableView1 = [[UITableView alloc] initWithFrame: self.view.frame];
lTableView1.scrollsToTop = YES;
[lViewController1.view addSubview: lTableView1];
lTableView1.dataSource = self;
[self.view addSubview: lViewController1.view];
[self addChildViewController: lViewController1];
lTableView1.tag=1;
UIViewController *lViewController2 = [[UIViewController alloc] init];
UITableView *lTableView2 = [[UITableView alloc] initWithFrame: self.view.frame];
lTableView2.scrollsToTop = NO;
[lViewController2.view addSubview: lTableView2];
lTableView2.dataSource = self;
[self.view addSubview: lViewController2.view];
[self addChildViewController: lViewController2];
lTableView2.tag=2;
// now hide the view in view controller 2
lViewController2.view.hidden = YES;
}
- (NSUInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSUInteger)section {
return 50;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString * const kCellIdentifier = #"MyCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kCellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kCellIdentifier];
}
cell.textLabel.text = [NSString stringWithFormat:#"hello %d %d",indexPath.row, tableView.tag];
return cell;
}
I am trying to add a static fixed image to a UITableViewController, but when I do the standard [self.view addSubview:imageView]; the image is placed on the tableview and moves with the scrolling.
Is there any way to do this so that the image stays fixed?
I know one method would be to create a UIViewController, then add the UIImageView and a UITableView, but unfortunately, I am using a custom UITableViewController (just a library found on gihub to do what I needed), so my controller must be a UITableViewController.
Is there any way to do this? I've been going at this for a while with no luck.
Cheers,
Brett
There is no problem using UIViewController idea. You just keep 2 view controllers: 1) UIViewController, which has the UIImageView inside, and subview the view of 2) the UITableViewController. If necessary, make the UITableViewController a strong reference of the UIViewController.
I have done something similar all the time.
Yes, there are few ways. You could create your view hierarchy programmatically at
viewDidLoad or use a NIB file. Make sure that you correctly link the delegates and view properties.
If a nib file is specified via the initWithNibName:bundle: method (which is declared by the superclass UIViewController), UITableViewController loads the table view archived in the nib file. Otherwise, it creates an unconfigured UITableView object with the correct dimensions and autoresize mask. You can access this view through the tableView property.
If a nib file containing the table view is loaded, the data source and delegate become those objects defined in the nib file (if any). If no nib file is specified or if the nib file defines no data source or delegate, UITableViewController sets the data source and the delegate of the table view to self.
As https://stackoverflow.com/a/6961973/127493 say, UITableViewControllers can be replaced by simple UIViewControllers.
In fact, the trick is to add an UITableView to you UIViewController, make it delegate and etc..., and add it to your UIViewController.view.
So you will be able to add some "sister" views to your controller main view.
In my case, I am adding a an Image ( actually button with image) and when user touches on image, it will disappear and tableview will be shown.
so i am disabling scroll first then enable it back
find code below
// in viewDidLoad
[self.view addSubview:imgview];
tbl.scrollEnabled = NO;
// in -(IBAction)btnClicked:(id)sender
[imgview removeFromSuperview];
tbl.scrollEnabled = YES;
Thats working for me.
Do NOT use UITableViewController at all (I never use it and as I've heard nearly any developer uses it). It is a nightmare when you want to customize design with it.
Create your own subclass of UIViewController (MYTableViewController), add UITableView *tableView instance #property and #synthetize it:
#interface MYTableViewController : UIViewController <UITableViewDelegate,UITableViewDataSource> {
UITableView *tableView;
}
#property (nonatomic, retain) IBOutlet UITableView *tableView;
#end
Then in implementation add it to the view (using XIB or viewDidLoad method):
#implementation MYTableViewController
#synthesize tableView;
// If not XIB used:
-(void)viewDidLoad{
[super viewDidLoad];
CGRect frame = self.view.bounds;
self.tableView = [[[UITableView alloc] initWithFrame:frame style:UITableViewStylePlain] autorelease];
tableView.dataSource = self;
tableView.delegate = self;
[self.view addSubview:tableView];
// And here you van add your image:
[self.view addSubview:imageView];
}
// Do not forget to release it and clear delegate and datasourcce when view uloads:
#pragma mark - Memory management:
-(void)dealloc{
self.tableView.delegate = nil;
self.tableView.dataSource = nil;
self.tableView = nil;
[super dealloc];
}
- (void)didReceiveMemoryWarning {
self.tableView.delegate = nil;
self.tableView.dataSource = nil;
self.tableView = nil;
[super didReceiveMemoryWarning];
}
-(void)viewDidUnload{
self.tableView.delegate = nil;
self.tableView.dataSource = nil;
self.tableView = nil;
[super viewDidUnload];
}
#end
I've been trying to understand View Controllers and Views and even after watching some of the classes on iTunesU, I'm still having some trouble implementing them programmatically. I'm hoping someone can clarify a bit.
So I'm trying to create a UIViewController which in turn creates its view.
The program is broken up in the following classes:
ProgramNameAppDelegate.h and .m
ApplicationRootViewController.h and .m
From the AppDelegate, I create the UIWindow and UIViewController. Partial code goes like this:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
_window = [ [UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
if (!_window)
{
[self release];
return NO;
}
_rootViewController = [ [ApplicationRootViewController alloc] init];
if (!_rootViewController)
{
NSLog(#"No _rootViewController");
[self release];
return NO;
}
[_window addSubview:[_rootViewController view]];
[_window makeKeyAndVisible];
return YES;
}
In the ApplicationRootViewController I call init. My UIView is created in loadView as such:
- (void)loadView
{
NSLog(#"In loadView");
[super loadView];
CGRect _frame = [[UIScreen mainScreen] applicationFrame];
UIView* _rootView = [[UIView alloc] initWithFrame:_frame];
[_rootView setBackgroundColor:[UIColor redColor]];
self.view = _rootView;
return;
}
The problem I'm having is apparently the program is creating the view, however, it is never displaying the view that I created until the app resigns active. Once I go out of the app and come back in, the view is there. I have tried several other things, but it always behaves the same.
I would eventually like to for the controller to create the view from a subclassed UIView.h and .m file.
Thanks,
Kevin
From the docs:
Your custom implementation of loadView
method should not call super.
So, get rid of [super loadview] and it should work;)
Also, if you want to use a custom view (Subclass of UIView). Alloc/Init using initWithFrame: and when referring self.view from uiviewcontroller you will have to cast it like so:
[(MyView *)self.view myMethod];
As simple as that ;)
EDIT:
Suppose you make a class like this:
//MyView.h
#interface MyView : UIView{
...
}
- (void) doSomething:(NSString *)string;
#end
//MyView.m
#import MyView.h
#implementation MyView
... write your implementation here
#end
then in the UIViewController,
in loadView
do:
//don't forget to #import "MyView.h"
-(void) loadView{
MyView *myView = [[MyView alloc] initWithFrame:CGRectMake(...)];
self.view = (UIView *)myView;
[myView release];
}
then when referring your view somewhere else in the controller:
- (void) viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
[(MyView *)self.view doSomething:#"something"];//1
[self.view setBackgroundColor:[UIColor greenColor]];//2
}
In //1 you should cast because your are calling doSomething: method (or it could be a property as well), which is declared/defined in MyView and not in UIView. If you don't cast you will get a warning but it will work.
In //2 you don't need to cast since setBackgroundColor: is a method defined in UIView class ;)
Objective-C is very flexible and will allow to cast many things, so you have to be careful because casting is like telling the compiler: "trust me, is not a UIView, is MyVIew" and the compiler will obey you. But if you were wrong your app will crash when attempting to call doSomething: because it does not exist in UIView.
I've been working on a simple iPhone app for a couple of days now, and haven't been able to wrap my head around quite a bit of the interface. Specifically, I've got a main menu view with an ImageView and a couple of buttons that will eventually swap out to other views.
In addition to the main menu, I've got a UIViewController subclass called Browse_Phone (it's a Universal app), and it contains a UITableView called tableView. It'll eventually be hooked up to a database, but for now, the contents of the table are hard-coded. The following is the table delegate code in Browse_Phone.m (most of this code is borrowed from online examples):
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 5;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
}
NSString *szCell = [[NSString alloc] initWithFormat: #"Row %i", indexPath.row ];
cell.textLabel.text = szCell;
[szCell release];
// Set up the cell
return cell;
}
// The designated initializer. Override if you create the controller programmatically and want to perform customization that is not appropriate for viewDidLoad.
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])
{
self.title = #"Browse";
tableView = [[UITableView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
tableView.delegate = self;
tableView.dataSource = self;
self.view = tableView;
}
return self;
}
Finally, in the main window, there's a button to swap out the main menu view with a navigation controller that uses a Browse_Phone controller as its RootViewController (note that [sender superview] is the main menu view):
- (IBAction)loadBrowse:(id)sender
{
Browse_Phone *browsePhone = [[[Browse_Phone alloc] initWithNibName:nil bundle:nil] autorelease];
if(browsePhone.view)
{
UINavigationController *navController = [[[UINavigationController alloc] initWithRootViewController:browsePhone] autorelease];
CGContextRef context = UIGraphicsGetCurrentContext();
[UIView beginAnimations:nil context:context];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDuration:0.5];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:window cache:YES];
[[sender superview] removeFromSuperview];
[window addSubview:navController.view];
[UIView commitAnimations];
}
}
When I press the button, the view swap seems to work. The flip occurs as expected, and the navigation control is loaded, but the table is nowhere to be found. I know the Browse_Phone controller is being instantiated, because the navigation bar's title reflects that of the table. When I used this code in a simple app that just loaded the controllers in the app delegate's didFinishLaunchingWithOptions, it worked just fine. Of course, I know the problem is going to be something simple that I've missed.
Thanks in Advance,
Ryan
It's your initWithNibName:bundle: method.
loadView gets called after initWithNibName:bundle: and overrides your view. You should load your view in loadView, or better yet, subclass UITableViewController (instead of UIViewController) and let it do everything for you.
All right, I think I understand the problem; it looks like somewhere during the creation and initialization of the view controllers, they're being set to autorelease, so when I explicitly set them to autorelease, they wind up being released one too many times. So before the table gets a chance to render, the controller's retain count drops to 0 and it gets dumped. Removing the autorelease from each controller's initialization in loadBrowse fixes the problem. I can't say I understand why the app doesn't just crash outright, but the problem appears to be solved for the time being.