Load an UIView or a UITableView based on one variable - iphone

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...

Related

iPhone Storyboard Editing a table view

I've been trying to learn the new Storyboard feature in Xcode and I've run into a problem with trying to set a UITableView to edit mode.
So far my storyboard looks like this:
NavigationController -> UIViewController (subclass with tableview property)
I added a Navigation Item and a Bar Button item to the view controller scene, so I do see an edit button. It didn't do anything automagically, so I tried linking it's selector to the setEditing method of the tableview delegate. This did put it into editing mode. However, the edit button did not change to a "Done" button and so there is no way to get out of editing mode.
Do I have to create another Navigation item for the Done button? How do I connect it so that it appears at the right time and works correctly?
I think that also with Storyboard, the only way (for sure, the easiest one) to implement a working edit/done button, is to use the following code:
- (void)viewDidLoad
{
[super viewDidLoad];
...
//set the edit button
self.navigationItem.leftBarButtonItem = self.editButtonItem;
...
This is the solution that Apple itself implements if you select a "Master-Detail Application" template for your project.
Probably Storyboard is still not perfect, and hopefully it will be improved from Apple in next releases...
I just started using Storyboards, so I also wanted to use the Storyboard to add my Edit button. It is annoying to have taken the time to learn how to use a new tool but find you need a roll of duct tape to patch up the holes.
You can get it to work, but need to add a Custom button. In the Attributes inspector make sure the Identifier is Custom and the title is Edit.
Then add something like this in your .m
- (IBAction)setEditMode:(UIBarButtonItem *)sender {
if (self.editing) {
sender.title = #"Edit";
[super setEditing:NO animated:YES];
} else {
sender.title = #"Done";
[super setEditing:YES animated:YES];
}
}
Have your Custom Edit button call the setEditMode method.
Can only hope they will fix the implementation of the Edit button in the Storyboard editor in the future.
To summarize:
The Button, returned by UIViewController.editButtonItem is a special toggling button with special behavior that calls - (void)setEditing:(BOOL)editing animated:(BOOL)animated if pressed.
The Button, returned by UINavigationController.editButtonItem is a simple Button, just labeled with "Edit".
The Storyboard allows to select the latter one.
If you are using the navigation controller to push to the view controller, simply set self.navigationItem.rightBarButtonItem = self.editButtonItem;, which will put the default Edit button in the right. If the navigation bar is not visible, call self.navigationController.navigationBarHidden = NO;. Those would be called in the viewDidLoad method, or something similar. Then in order to get the tableView to respond to the edit call, use the following method:
- (void)setEditing:(BOOL)editing animated:(BOOL)animated {
[super setEditing:editing animated:animated];
[tableView setEditing:editing animated:animated];
}
That should do what you want it to do. If you have any issues, just say so and we can narrow down the details
To add to #Graham answer, you might also want to change the style so you can have the "Done" button style (the blue color). Something like this:
- (IBAction)setEditMode:(UIBarButtonItem *)sender {
if (self.editing) {
sender.title = #"Edit";
sender.style = UIBarButtonItemStylePlain;
[super setEditing:NO animated:YES];
} else {
sender.title = #"Done";
sender.style = UIBarButtonItemStyleDone;
[super setEditing:YES animated:YES];
}
}
one can use the dumb, not working Edit button from the Storyboard editor and then programmatically replace it with the UIViewController.editButtonItem.
in viewDidLoad:
NSMutableArray *toolbarItems = [NSMutableArray arrayWithArray:self.toolbarItems];
[toolbarItems replaceObjectAtIndex:0 withObject:self.editButtonItem];
[self setToolbarItems:toolbarItems];
this code assumes one has added the dumb Edit button as the leftmost item on the toolbar in the Storyboard.
In case that you have UIViewController and inside this you added a UITableVIew.
If you want to add an edit UIBarButton in order to interact with UITableView, try:
Add this line...
- (void)viewDidLoad
{
[super viewDidLoad];
...
self.navigationItem.leftBarButtonItem = self.editButtonItem;
...
}
and this method
- (void)setEditing:(BOOL)editing animated:(BOOL)animated {
[super setEditing:editing animated:animated];
[self.myListTableView setEditing:editing animated:animated];
if(self.myListTableView.editing) {
NSLog(#"editMode on");
} else {
NSLog(#"editMode off");
}
}
where
#property (weak, nonatomic) IBOutlet UITableView *myListTableView;

Delete button in UIViewController which is showing the detail of a cell

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

updating uitableview from the custom cell after changing it

I've implemented edit menu from my custom UITableViewCell class. I have a small problem of updating a table view from inside the custom table cell class. What is the best approach to do that?
TIA
Clarification: By edit menu I meant a standard Cut/Copy/Paste... menu, that can complies with a standard UIResponder protocol. I want to cut/copy/paste cells content, which resides in some data structure (kind of folders/files tree). The cell actually only reflects the data.
The menu shows up on tap & hold on table cell. The table is derived from UITableViewController and created on fly (not from the xib). Cut/Copy actions are allowed for folders & files, while Paste action is allowed only for folders. Actually I need to refresh only the folder cell, which shows the number of items inside.
So in my CustomCell in paste selector I do the following:
- (void)paste:(id)sender {
... Perform a paste of data...
MyTableViewController *myTable = (MyTableViewController *) delegate;
[myTable performSelector:#selector(updateData) withObject:nil afterDelay:0.3];
}
In MyTableViewController:
- (void) updateData
{
[self.tableView reloadData];
}
The thing is that all cells except of the one that was changed are redrawn. I see it in cellForRowAtIndex function. Even if I add in paste selector [self setNeedsDisplay] it doesn't help.
Also, my custom cell overrides setHighlighted function:
- (void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated {
if (delegate)
[delegate copyableTableViewCell:self willHighlight:highlighted];
[super setHighlighted:highlighted animated:animated];
}
so the delegate (MyTableViewController) shows an edit menu there.
And again the question is why the changed cell doesn't refresh?
Thanks
Do you want to update a single cell or the whole tableview? What about some kind of delegates, or selectors?
Resolved. Calling in MyTableViewController:
- (void) updateData
{
[self.tableView performSelector:#selector(reloadData) withObject:nil afterDelay:0.3];
}
and it does the work...

Cancel out of UISearchBar when user taps on view

Does anyone know how to cancel (resign First Responder) out of a UISearchBar when you tap below the search text box and above the keyboard? Can anyone help post some code to handle this?
Thanks
Add a tap gesture in the parent view (of the UISearchbar)
[self addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:searchBar action:#selector(resignFirstResponder)]];
I accomplished this by using a UITapGestureRecognizer:
UIGestureRecognizer* cancelGesture;
- (void) backgroundTouched:(id)sender {
[self.view endEditing:YES];
}
#pragma mark - UISearchBarDelegate
-(void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar {
cancelGesture = [UITapGestureRecognizer new];
[cancelGesture addTarget:self action:#selector(backgroundTouched:)];
[self.view addGestureRecognizer:cancelGesture];
}
-(void)searchBarTextDidEndEditing:(UISearchBar *)searchBar {
if (cancelGesture) {
[self.view removeGestureRecognizer:cancelGesture];
[cancelGesture release];
cancelGesture = nil;
}
}
The code is a bare, but you can see the intent. When the SearchBar starts editing, you attach a tap gesture recognizer to the view controller's view, and remove it when it stops editing.
There are a couple caveats that you can work around: doing this will make it so if you click anything besides the keyboard or the search bar's text field, the recognizer traps the click -- so if you use the clear, cancel, scope or results button they won't respond correctly.
In my particular scenario, I had a UITableView that was covering the exposed area of the view so I attached the gesture recognizer to it instead of the view controllers main view, isolating the area to which the gesture would respond.
An alternative idea I got from iphonedevbook, sample code project 04, was to use one big transparent button that lies behind all other controls which does nothing but resign all first responders if tapped. I.e. if the user taps anywhere where there isn't a more important control - which is the intuitive behavior - the search bar and keyboard disappear.
I ended up using a hybrid of Hauke's and Beau Scott's approach. There were two problems I ran into using their solutions:
1) If there's anything else on the screen, tapping it won't result in resignFirstResponder being called. For example, if the user taps a button rather than the space around the button, the button will eat the event. Beau Scott's solution addresses this issue, however.
2) Tapping the search bar itself will result in resignFirstResponder getting called. Clearly you don't want the keyboard to disappear when you tap UISearchBar. A small change described below addresses this.
I ended up setting up my view as follows. The parent view has two children - the UISearchBar and a subview which holds the rest of my UI elements. The subview takes up the entire screen below the UISearchBar. Then I used Beau Scott's exact code to add and remove the gesture recognizer, but instead of adding it to self.view I added it to the subview:
IBOutlet UIView *gestureRecognizer;
...
-(void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar {
cancelGesture = [UITapGestureRecognizer new];
[cancelGesture addTarget:self action:#selector(backgroundTouch:)];
[gestureRecognizer addGestureRecognizer:cancelGesture];
}
-(void)searchBarTextDidEndEditing:(UISearchBar *)searchBar {
if (cancelGesture) {
[gestureRecognizer removeGestureRecognizer:cancelGesture];
[cancelGesture release];
cancelGesture = nil;
}
}
First, you need a reference to the search bar. Let's assume that your controller object has an object reference UISearchBar *theSearchBar, and that you assign it when you create the UISearchBar object.
Next, you need to detect that the containing view has been touched. The view that is touched "knows", but you need get that information to the controller. Sadly, Apple didn't provide a simple way to do this, but it's not that hard either.
My solution is to replace the standard UIView that a UIViewController object normally creates with a UIControl, and then make the UIViewController respond to touch events.
MainController.m
- (void) loadView {
UIControl *control = [[UIControl alloc] initWithFrame: <desired frame>];
[control addTarget: self action: #selector(touchUpInside)
forControlEvents: UIControlEventTouchUpInside];
// or touch down events, or whatever you like
self.view = control;
[control release];
}
- (void) viewDidLoad {
[super viewDidLoad];
theSearchBar = [[UISearchBar alloc] initWithFrame: <desired frame>];
// insert code to finish customizing the search bar
[self.view addSubview: theSearchBar];
}
- (void) touchUpInside {
if [theSearchBar isFirstResponder] {
// grab any data you need from the search bar
[theSearchBar resignFirstResponder];
}
}
MainController.h
#interface MainController : UIViewController
{
UISearchBar *theSearchBar;
}
Clarification:
There is only a single object -- let's call the class MainController -- which is a subclass of UIViewController. All of the methods listed above are implemented in MainController. theSearchBar is declared as a UISearchBar* in the .h file.
Are you defining your view and controller using Interface Builder? If so, I suggest you learn how to NOT use it -- once you get into the kind of tricks we are discussing here, it becomes more of a hindrance than a help -- I don't use it at all, ever.
#Gia Dang's answer is the simplest, but I don't subclass the UIView, only the UIViewController, so my call is slightly different. Also, since I don't know the overhead for actually calling resignFirstResponder, I prefer to check first. It's more code, but since all of this is done on the main thread (which can slow down the UI), I'd rather check first.
#implementation MyController : UIViewController {
#private
UISearchController *_uiSearchController;
}
- (void)viewDidLoad {
// add tap on view to resign the responder if we're in the middle of typing in the search
UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(closeKeyboardIfNeeded)];
[self.view addGestureRecognizer:tapGestureRecognizer];
}
- (void)closeKeyboardIfNeeded {
if (![_uiSearchController.searchBar isFirstResponder]) {
return;
}
[_uiSearchController.searchBar resignFirstResponder];
}
#end
As for the other answers, be careful about constantly recreating objects. There is always a performance hit, whether it's the creation itself or the garbage collection through ARC, and these things will slow down your main thread. Depending on what you're doing also on the main thread, it may have a significant performance impact.

How do I display a placeholder image when my UITableView has no data yet?

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!