scrollsToTop not working with UIViewController containment - iphone

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;
}

Related

Hide UIPopover after selecting row

I am new to iPad developer,
I am using UIPopover in my application, when i select any row in my popover, my popover is not getting hide it still in the view, it is getting hide when i touch outside anywhere on the screen.
I want to hide popover after user selects any row.
here is my code snippet,
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath
{
for(int index=0;index<=indexPath.row;index++)
{
UITableViewCell *cell =[tableView cellForRowAtIndexPath:indexPath];
lbl.text=cell.textLabel.text;
}
}
Logic: When i select any row of popover corresponding text will be fetched and gets stored into label.
Any help will be appreciated.
EDIT
UIViewController* popoverContent = [[UIViewController alloc]init];
UIView* popoverView = [[UIView alloc] initWithFrame:CGRectMake(110, 0, 500, 4)];
popoverPolicyNameTable = [[UITableView alloc]initWithFrame:CGRectMake(0, 0, 250, 200) style:UITableViewStylePlain];// size of tableview inside popover
[popoverPolicyNameTable setDelegate:(id<UITableViewDelegate>)self];
[popoverPolicyNameTable setDataSource:(id<UITableViewDataSource>)self];
[self.view addSubview:popoverPolicyNameTable];
[popoverPolicyNameTable release];
[popoverView addSubview:popoverPolicyNameTable];
popoverContent.view = popoverView;
popoverContent.contentSizeForViewInPopover = CGSizeMake(250, 200); //size of popover border
self.popoverController = [[UIPopoverController alloc] initWithContentViewController:popoverContent];
[self.popoverController presentPopoverFromRect:CGRectMake(350,100, 35, 35) inView:self.view permittedArrowDirections:UIPopoverArrowDirectionUp animated:YES]; //size of arrow
[popoverContent release];
[popoverView release];
You need to implement something like this
- (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController1{
[self.popoverController dismissPopoverAnimated:YES];
self.popoverController=nil;
}
Add one variable inside the Class which is having your UITableView:
id parent;
Then properties and synthesize it:
#property(nonatomic, retain) id parent;
#synthesize parent;
Then where you are creating the object of this ViewController to add in Popovercontroller, do like this
myTableViewController.parent = myPopoverControllerObject;
Now in didSelectRowAtIndexPath:
[parent dismissPopoverAnimated:YES];
And you are done!!!
Use delegation and notify your delegate that the user selected an item in the list. The delegate will know how to dismiss the view controller.
This approach is better because it's container agnostic, and you can reuse your view controller outside a popover: for example, using navigation controller on iPhone
Make Popover controller as class member variable.
Then on clicking on table view click method call dismiss method of that popovercontoller.

Tableview lines show trough loading view

Situation:
I'm trying to display a loading screen while waiting for my asynchronous connection to return with data to populate the tableview.
Problem:
Creating and adding the loadingscreen works fine, however, the tableview draws its lines over it, see screenshot:
.
Code: I add the view with these lines:
-(void) viewDidLoad{
[super viewDidLoad];
_loadScreen = [[LoadScreen alloc] initWithFrame:self.view.bounds];
[self.view addSubview: _loadScreen];
[self fetchRemoteData];
}
Question: Is it possible to add the loading view ontop of the table? Or can i make sure the tableview does not draw its lines untill i call reloadData?
-Thanks in advance,
W
I've done it like this many times:
-(void) viewDidLoad{
[super viewDidLoad];
_loadScreen = [[LoadScreen alloc] initWithFrame:self.view.bounds];
[self.tableView addSubview: _loadScreen];
self.tableView.hidden = YES;
[self fetchRemoteData];
}
- (void)dataFetchedSuccessfully
{
self.tableView.hidden = NO;
}
Just hide the tableview and show it again when the data has been loaded.
There is some approaches that will solve you problem:
-Set a footer view for the table view, so all lines should disappear.
self.tableView.tableFooterView = [[[UIView alloc] initWithFrame:CGRectZero] autorelease];
-I assume that you use UItableViewController. If so self.view and self.tableView both represents the same view, so by setting:
self.tableView.hidden = NO;
It will hide even your loading view. What I encoure you to do is to create a custom view which has an table view as its subview. Then you can hide this table view by only showing an loading view.
Hope I could help.

How do I invoke the editing properties of a UITableView programmatically

I got a UITableView on my app without using a NIB. However, because I do it this way, I can't get the editing properties usually associated with a regular UITableView. For example, if I instead use #interface TableViewController:UITableViewContoller and add a editButtonItem on the navigation bar, the delete and moving rows will automatically be included once I press on that button.
However, nothing works on my UITableView. Please help.
// in .h
#interface TableViewController : UIViewController
<UITableViewDelegate, UITableViewDataSource>
{
UITableView *tView;
NSMutableArray *aMutArray;
}
// in .m
-(id)init
{
[super initWithNibName:nil bundle:nil];
[[self navigationItem] setLeftBarButtonItem:[self editButtonItem]];
[[self navigationItem] setTitle:#"ReorderingTableCell"];
return self;
}
- (void)loadView
{
[super loadView];
CGRect tableFrame = CGRectMake(0, 0, 320, 300);
tView = [[UITableView alloc]initWithFrame:tableFrame style:UITableViewStylePlain];
[[self tView] setDelegate:self];
[[self tView] setDataSource:self];
aMutArray = [[NSMutableArray alloc] initWithObjects:#"first", #"second", #"third", nil];
[[self view] addSubview:tView];
}
and then a bunch of delegate methods like:
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath,
- (void)setEditing:(BOOL)flag animated:(BOOL)animated
etc...
I don't want to go through the details of the delegate methods because I create a new project with a simple UITableViewController and it works.
BTW, when I run the code, I set breakpoints and the delegate methods are called but nothing happens. It does not give me the delete icon on the left of the cell and no moving cell icon on the right side. Nothing happens.
Thanks so much!!!
You have to call -[UITableView setEditing:animated:] to bring the table view in and out of edit mode.

Missing UITableView after swapping in a UINavigationController

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.

iPhone UITableView with a header area

I have a view that was created with all of the default UITableView stuff, but now I need to add a header area above where the UITableView is (so the UITableView will scroll normally, but the top 100px of the screen or so will have static header content). I don't see where I can resize the UITableView in IB, and am not sure how to do this.
Does anyone know?
You can use UITableViewDelegate methods to create a custom header view for a table and specify the height, namely tableView:viewForHeaderInSection: and tableView:heightForHeaderInSection:. You can add whatever you like to the view. Here's an example that adds a right aligned UILabel:
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
UIView *headerView = [[UIView alloc] initWithFrame:CGRectMake(0,0,tableView.frame.size.width,30)];
UILabel *headerLabel = [[UILabel alloc] initWithFrame:CGRectMake(60, 0, headerView.frame.size.width-120.0, headerView.frame.size.height)];
headerLabel.textAlignment = NSTextAlignmentRight;
headerLabel.text = [titleArray objectAtIndex:section];
headerLabel.backgroundColor = [UIColor clearColor];
[headerView addSubview:headerLabel];
return headerView;
}
-(CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
return 30.0;
}
Why don't you use the UITableView provided header?. As follow:
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
return #"My Title";
}
Additionally you may resize your table view in IB by dragging the borders.
When you add a UIView or one of its subclasses onto the UITableView using IB (just drag a UIView and drop it onto the UPPER part of UITableView of yours), it automatically adds that UIView component and makes it the "tableHeader" component.
Each UITableView has one tableHeader and one tableFooter component reserved...
This way the new view would be a part of the UITable, and scroll with it or appear/disappear or whatever you do to the table. You can change its hidden property if you need conditional behavior.
On the other hand, if you want the header view stay put, as the table scrolls, then it is better to make the table smaller and put the header above it as suggested in other answers...
I finally solved this problem the right way without changing the base class. The one answer to add the view to the parent nav controller is nice but the transitions look horrible.
The fix is actually easy. The trick is to create custom setter and getter for self.tableView property. Then, in loadView, you replace the view with a fresh UIView and add the tableView to it. Then you're free to add subviews around the tableView. Here's how it's done:
In header:
#interface CustomTableViewController : UITableViewController
{
UITableView *tableView;
}
In .m:
- (UITableView*)tableView
{
return tableView;
}
- (void)setTableView:(UITableView *)newTableView
{
if ( newTableView != tableView )
{
[tableView release];
tableView = [newTableView retain];
}
}
- (void)loadView {
[super loadView];
//save current tableview, then replace view with a regular uiview
self.tableView = (UITableView*)self.view;
self.view = [[UIView alloc] initWithFrame:self.tableView.frame];
[self.view addSubview:self.tableView];
//code below adds some custom stuff above the table
UIView *customHeader = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 20)];
customHeader.backgroundColor = [UIColor redColor];
[self.view addSubview:customHeader];
[customHeader release];
self.tableView.frame = CGRectMake(0, customHeader.frame.size.height, self.view.frame.size.width, self.view.frame.size.height - customHeader.frame.size.height);
}
- (void)viewDidUnload
{
self.tableView = nil;
[super viewDidUnload];
}
Enjoy!
You will have to embed the UITableView in a UIView alongwith another view (which you are referring to as header section).
So, the UIView will have 2 subviews. The header view followed by the table view.
UIView(parent)
UIView (header)
UITableView (table)
Hope this helps.
I like the answer from noodl_es (upvoted), because it provides the functionality and behavior you want, yet you don't have to worry about resizing the UITableView: that is handled for you automatically. However, the solution is best suitable only if the header information pertains specifically to the first section of the table (or if the table has only one section). If the table has more than one section, then the header of the second section will push away the header of the first section when scrolled up, and therefore the header view will not appear to pertain to the whole table.
Found a solution at iphonedevsdk
Instead of doing this:
[tableViewController.view addSubview:viewSubclass];
do this
[tableViewController.navigationController.view addSubview:viewSubclass];
Suppose to have your UITableViewController
#interface MXMTableViewController : UITableViewController <UITableViewDelegate,UIScrollViewDelegate> {
/// your table view interface here
}
and a xib with you simple UITableView defined yet in it, you can do as Mihir says overriding the loadView method like this:
- (void)loadView {
[super loadView];
UIView *mainView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 480)];
self.view = mainView;
[mainView release];
// Add Header View
UIView *headerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 36)];
headerView.backgroundColor = [UIColor redColor];
[self.view addSubview:headerView];
// now, move your table view down. Check you nib to choose
// the right Y-axis offset
CGRect f = tableView.frame;
f.origin.y += headerView.frame.size.height/2;
tableView.frame = f;
// Add the table view to the container view
[self.view addSubview:self.tableView];
// Add footer
UIView *footerView = [[UIView alloc] initWithFrame:CGRectMake(0, self.tableView.frame.size.height, 320, 125)];
footerView.backgroundColor = [UIColor redColor];
[self.view addSubview:footerView];
[footerView release];
[headerView release];
}
...and that's it. You have a UITableView with fixed header and footer.
PS. You may now use your xib custom views as the header and footer's views.