In my project I have some scroll views and a tableviews. I have implemented delegate method - (void)scrollViewDidScroll:(UIScrollView *)scrollView It is called by scroll view and also by tableview,(Since tableview is a subclass of scroll view). I want to know the method is not called by table view. How can I do so?
A simple check like this one should do the trick:
if ([scrollView isKindOfClass:[UITableView class]] == YES) {
// table
}
else {
// scroll view
}
Or set & compare tags for multiple tables/scroll views
Related
I have a UITableViewController which contains a View and a Table View Section.
View contains a label that indicates the title of the table.
My problem is that the scroll includes the View. What I want is to keep View static (exclude from scrolling) and to scroll only Table. (I use static cells)
Thanks.
The hierarchy of a UITableViewController is
- UIView
-- UIScrollView
---- UITableView
Initially you're in the UITableView when modifying items, so you'll want to add the portion that you do not want to scroll to the UIView (outside of our scrollView). So you'll need to call super a couple times like this:
[self.superview.superview.view addSubview:viewThatDoesNotScroll];
Since UITableView is a subclass of UIScrollView:
- (void)viewDidLoad {
[super viewDidLoad];
// mySubview is an instance variable, declared in .h file
[self.tableView addSubview:mySubview];
// here goes the rest of your code
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if(scrollView == self.tableView) {
mySubview.frame = CGRectMake(mySubview.frame.origin.x, scrollView.contentOffset.y, mySubview.frame.size.width, mySubview.frame.size.height);
}
}
The code was taken from WWDC '10 or '11 (I don't remember), so I'm sure it's the most appropriate way to do it.
Explanation: In -viewDidLoad you create your view and add it as a subview of your tableView. You can do it in -loadView or -init - it doesn't matter. The most important lines are in the -scrollViewDidScroll: method. This method is called whenever user drags the scrollView, so you can simply set the origin.y of your subview to contentOffset.y of the scrollView.
Do not UITableViewController. Use UIViewController and manage the views outside of the UITableView object. If you need, you can also implement UIViewControllerContainment to manage different views and different view controllers inside your custom view controller.
I have an application, in which I have a UITableView inside a UIView, which is again inside a UIScrollView, so the hierarchy becomes like this:
UIScrollView -> UIView -> UITableView
The data inside my UITableView is filled properly.
Now, my problem is that, When I scroll my UITableView, the UIScrollView's delegate method scrollViewDidEndDecelerating: and scrollViewDidEndDragging:: gets called.
I don't want this behavior, what should I do to stop this behavior?
Any one please Help,
Thank in advance!!!
UITableViewDelegate extends UIScrollViewDelegate. Hence the calling of the delegate methods.
To stop this you can set tableView.tag = 1000; when you alloc the tableView and in the UIScrollViewDelegate methods ( scrollViewDidEndDecelerating: and scrollViewDidEndDragging:: )add this at the very begining:
if(scrollView.tag == 1000)
return;
Because UITableView inherits from UIScrollView. So it shows all the properties and behaviours of UIScrollView. If you dont want this then please do one thing.
Assuming you have another scrollview in your page.
in the viewDidLoad or from the XIB (if you have your tableview in the XIB), set a tag for your tableview.
in code,
self.objYourTableView.tag = 101;
then in the scroll view delegate
- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView
{
if(!(scrollView.tag == 101))
{
// Go for your code to run.
}
}
So that your code will skip if it called by the table view. Other cases it works perfect. Hope this will help you.
I have an iPhone app that has a tableview which contains cells that when touched show a detail of that object. I would like to add a Delete button to the bottom of the detail view. When the user presses it the object which is represented by the cell should be removed and the app should return to the TableView.
In terms of best practices, which is the ideal way to accomplish this?
There are few ways in which you can signal the deletion. One of them is delegates. You can define your delegate like this,
#protocol DetailViewControllerDelegate
- (void)shouldDeleteDetailObject:(id)object
#end
And then your table view controller subclass adopt the protocol and implement the method like,
- (void)shouldDeleteDetailObject:(id)object {
[self.objectsArray removeObject:object];
[self.navigationController popViewControllerAnimated:YES];
}
And then you message [self.tableView reloadData]; in viewWillAppear: as sandy has indicated.
Your button action will be implemented as,
- (IBAction)deleteObject {
if ( self.delegate && [self.delegate respondsToSelector:#selector(shouldDeleteDetailObject:)] ) {
[self.delegate shouldDeleteDetailObject:self.detailObject];
}
}
And delegate should be an assigned property.
You can also look at notifications but this is a better route for this situation.
I think there is nothing serious about this, if you successfully delete the particular details after that on backing on previous view (tableview) you just use this
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self.tableView reloadData];
}
My app has a "favorites" tab. Inside, of course, there are the items (in a UITableView) that the user has set to "favorite".
My problem is this: when, at the starts, the user has no favorites, i want to show an UIView (with an UIButton "add favorite") and not the UITableView...
is it possible?
Sure, it will be a simple conditional in your code, preferably within the viewWillAppear: method. Something like the following:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
if (self.hasFavorites) {
// add UITableView as a subview
} else {
// add UIButton as a subview
}
}
Yes, this is possible. I'm assuming you have the user's favorite store in some kind of Array. So simply:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
//Reload the array here.
[self loadFavoriteArray];
if (_favoriteArray == nil || [_favoriteArray count] < 1) {
[_favoriteTableView setHidden:YES];
[_noFavoriteView setHidden:NO];
}
else {
[_favoriteTableView setHidden:NO];
[_noFavoriteView setHidden:YES];
}
}
How is your view set up - UIViewController or UITableViewController? If I understand your question correctly, I think you have a couple options.
1) You could set the tableHeaderView. This would allow you to have a button above the table which could be populated with a single cell that reads 'No Favorites'...or something like that. Would allow you to remove the button if they have a favorite listed, or you could even leave it there for good as a quick way to add additional favorites
2) Assuming your using a UIViewController (and subsequently adding your UITableView), and not UITableViewController, you could simply just not add the UITableView subview if they don't have any favorites.
Hope that helps...
I have an application that, on load, displays a UITableView with the user's data in it.
However, when the user first loads the application (before they've created any data), I'd like to display, instead of an empty table, a background image (with an arrow pointing to the 'add a record' navbar button). Once the user has added their first record, the tableview is displayed instead. I've seen numerous apps do this - the only example I can think of at present is Cha-Ching, before you have any budgets/etc set up. I can't for the life of me work out how to do this, though.
I initially added a navigationcontroller to the main window's xib, the rootViewController of which was a custom viewcontroller/xib. This rootViewController contained the background image with a hidden tableview above it, and a custom tableviewcontroller that managed the tableview. This seemed to work just fine, and if there was data it would load and display in the table. However, if I was to scroll the data offscreen, the app would crash, with this error:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason:
'*** -[UITextEffectsWindow tableView:cellForRowAtIndexPath:]: unrecognized selector sent to instance 0xd2d130'
I have no clue what a UITextEffectsWindow is, or why it was trying to manage my tableview. I presume something may be hooked up incorrectly in my view hierarchy...
If there's a much simpler/more straightforward way of doing this, I'd be very grateful if someone could explain it. How would you do this?
Thanks in advance.
Here's one solution that I've been satisfied with so far.
First, I created a transparent view that was the same size as my TableView. I add this view as a sibling of the TableView whenever the TableView's data source has no data in it. I completely remove the view when data is available because transparency can affect the smoothness of the scrolling animation with TableViews.
I simply added a transparent label to this view that says something to the effect of "No Data Available". Adding a little shadowing to this label really helped to reinforce the concept that the label was 'floating' over top of the empty TableView.
I like your method of using an image though. Done properly, it might save you some work if you don't have to localize a string like I currently do.
To achieve this using a UITableViewController subclass as my only view (within a UINavigationController as per the Apple template) I used the following approach:
Create a UIView of the size of my tableView in the XIB that contains your UITableViewController and tableView.
Add an ImageView set with my placeholder image to the UIView.
Wire up the UIView as an IBOutlet (in the example code below, I called it emptyTableView)
When it is time to show the placeholder from within the UITableViewController subclass :
[self.tableView addSubView:emptyTableView];
[self.tableView setScrollEnabled:FALSE];
Disabling the scroll is necessary otherwise the user will be able to move the placeholder image up and down. Just remember to enable it once the user adds an item.
To remove the image view
[emptyTableView removeFromSuperview];
To do this, I use the following controller instead of UITableViewController. It will automatically place a view over the table when it is empty, and remove it when it is filled.
Just call [self reloadData] instead of [self.tableView reloadData] so that it can check if the table became empty.
In your subclass, implement a makeEmptyOverlayView function that will create the view to show over an empty table.
#interface MyTableViewController : UITableViewController
{
BOOL hasAppeared;
BOOL scrollWasEnabled;
UIView *emptyOverlay;
}
- (void) reloadData;
- (void) checkEmpty;
#end
#implementation MyTableViewController
- (void)viewWillAppear:(BOOL)animated
{
[self reloadData];
[super viewWillAppear: animated];
}
- (void)viewDidAppear:(BOOL)animated
{
hasAppeared = YES;
[super viewDidAppear: animated];
[self checkEmpty];
}
- (void)viewDidUnload
{
if (emptyOverlay)
{
self.tableView.scrollEnabled = scrollWasEnabled;
[emptyOverlay removeFromSuperview];
emptyOverlay = nil;
}
}
- (void) reloadData
{
[self.tableView reloadData];
if (hasAppeared &&
[self respondsToSelector: #selector(makeEmptyOverlayView)])
[self checkEmpty];
}
- (void) checkEmpty
{
BOOL isEmpty(YES);
id<UITableViewDataSource> src(self.tableView.dataSource);
NSInteger sections(1);
if ([src respondsToSelector: #selector(numberOfSectionsInTableView:)])
sections = [src numberOfSectionsInTableView: self.tableView];
for (int i(0); i<sections; ++i)
{
NSInteger rows([src tableView: self.tableView numberOfRowsInSection: i]);
if (rows)
isEmpty = NO;
}
if (!isEmpty != !emptyOverlay)
{
if (isEmpty)
{
scrollWasEnabled = self.tableView.scrollEnabled;
self.tableView.scrollEnabled = NO;
emptyOverlay = [self makeEmptyOverlayView];
[self.tableView addSubview: emptyOverlay];
[emptyOverlay release];
}
else
{
self.tableView.scrollEnabled = scrollWasEnabled;
[emptyOverlay removeFromSuperview];
emptyOverlay = nil;
}
}
else if (isEmpty)
{
// Make sure it is still above all siblings.
[emptyOverlay retain];
[emptyOverlay removeFromSuperview];
[self.tableView addSubview: emptyOverlay];
[emptyOverlay release];
}
}
#end
If you use Three20, you can easily set any image you want as a place holder prior to your table being populated.
So, to solve this I did as discussed in the comments above:
I created a normal UIViewController subclass, which contained a UIImageView and a UITableView. The viewController conforms to the UITableViewDelegate and UITableViewDatasource protocols, and looks after the tableView. The viewController class simply shows or hides the imageView depending on whether data is available.
I was going wrong before by trying to manage both these views with a UITableViewController. A UITableViewController has to have a tableView as its view, whereas, with this solution, a viewController can contain both the image and the tableView, and implement the necessary protocols to manage the tableView.
Thanks for all the help!