It's easy enough to set up a table view to allow editing. Just add one line to your UITableViewController:
self.navigationItem.rightBarButtonItem = self.editButtonItem;
This line adds an edit button on the nav bar that will change the table to editing mode and change its button text to "Done" while editing.
Is it possible to set this up in Interface Builder? I see that you can add a UIBarButtonItem and can set its "Identifier" to "Edit", but I don't see the expected behavior.
BTW, what does the "Identifier" in the Attributes panel do?
Yes, you can add UIBarButtonItems in Interface Builder, and they should work.
The identifier lets you use a preset button (like Edit or Reload), or you can choose Custom and make your own button.
EDIT: I may be able to help further if you could explain how UIBarButtonItems added through IB don't work.
UPDATE: UIViewController.editButtonItem is a special method that returns a UIBarButtonItem that invokes the view's setEditing method. You can achieve the same effect by creating a method that does the same thing and connecting the selector to your UIBarButtonItem in IB.
In your header file:
- IBAction edit:(id)sender;
and in your implementation file:
- (IBAction) edit:(id)sender {
[self setEditing:YES animated:YES];
}
then connect the selector to the UIBarButtonItem.
However, you might not be able to create this connection in the default Navigation-Based Application template since the Table View is in a separate file.
Have a look here: http://blog.tmro.net/2009/05/uitabbarbuttonitem-did-not-change-its.html
If you want your button to be able to change its label dynamically make sure you use a custom identifier otherwise its title will be immutable.
We still can't seem to do this specifically in Interface Builder as of Xcode 9.4.1. It's very easy to do in code, though.
You don't need to set up the button in IB at all. Simply add this code in your viewDidLoad method:
navigationItem.leftBarButtonItem = editButtonItem
That automatically sets up the Edit button, which turns to Done so the user can end editing.
To do anything custom associated with the editing process, override the view controller's setEditing(_ editing: Bool, animated: Bool) method.
For example, if you have a table view whose editing needs to be turned on and off, you can do this:
override func setEditing(_ editing: Bool, animated: Bool) {
super.setEditing(editing, animated: animated)
tableView.setEditing(editing, animated: animated)
}
Make sure to call super.setEditing here.
Note: if you're using a UITableViewController, setEditing is already set up in the super class to handle the table view. You don't need to override it, unless you have other custom code you want to include when editing is enabled/disabled.
Related
I have an NSTableView that is set to be 'view based', and within each NSTableCellView there is an NSButton and an NSTextField.
The text field is being populated correctly from an array controller. The buttons are appearing correctly but I'm having trouble working out how to hook up the click action.
I thought this would be possible by control-dragging from the NSButton in IB to a simple method like this one in my controller (in this case an NSDocument subclass):
- (IBAction)testAction:(NSButton *)sender {
NSLog(#"Test action");
}
It connects fine but never gets fired. Any ideas why this is or how to fix it?
I don't understand why this works, but I had the same problem and was able to get it working by assigning the table delegate and datasource to the file owner within IB, which is also the class of my click handlers. Only then did it seem to actually bind the click handlers for the buttons in my cell view. Previously I was setting the delegate and datasource in code after the view was loaded.
You have to subclass NSTableCellView class. put your onClick Action method in subclass files.
Let me know if i am not clear..
I have a textfield and a button. When I click inside the textfield, I want the button to disappear. I defined the textfield as both outlet and action ( with event “Did end on exit”). In the method for the textfield, I have self.testButton.hidden = YES; When I click inside the textfield, the button does not go away. Instead, it remains until I hit the return key on the keyboard – causing the keyboard to go away. I tried the same thing w/ touchup inside as the event on the text field. When you click in the text field, nothing happens to the button.
Instead of using the Target-Action mechanism ("Did end on exit" and "Touch Up Inside") use the Delegate mechanism.
First, make your class conform to the UITextFieldDelegate protocol. In your *.h (header) file add the following:
// Here I'm assuming your class is inheriting from UIViewcontroller but it
// may be inheriting from some other class. The really important part here
// is: <UITextFieldDelegate>. That's how you make your class conform to that protocol
#interface THE_NAME_OF_YOUR_CLASS : UIViewController <UITextFieldDelegate>
.
Second, implement the -(void)textFieldDidBeginEditing:(UITextField *)textField method. Also, remember to set yourself as the delegate too: self.textField.delegate = self. That way, the method will get called every time the user starts editing. Inside that methdod call self.testButton.hidden = YES;. In your *.m (implementation) file add the following:
-(void)viewDidLoad {
// here I'm assuming you have a 'strong' reference to your text field.
// You're going to need one to set yourself as the delegate.
self.textField.delegate = self;
}
// This is one of the methods defined in the UITextFieldDelegate protocol
-(void)textFieldDidBeginEditing:(UITextField *)textField {
self.testButton.hidden = YES;
}
.
Similarly, to make your button appear again, implement the - (void)textFieldDidEndEditing:(UITextField *)textField method. Inside it un-hide your button. Again, in your *.m file add the following:
// This is another method defined in the UITextFieldDelegate protocol
-(void)textFieldDidEndEditing:(UITextField *)textField {
self.testButton.hidden = NO;
}
Although delegates may be a mystery to you right now once you become familiar with them
you will realize they're very easy. And this is very important because iOS programming
relies heavily on delegates.
A delegate is a "notification" mechanism based on the "Hollywood" principle which is: don't call us; we'll call you.
In your case the class that contains the UITextField is interested in knowing when the UITextField begins editing and when it ends editing. But your class cannot be "polling" (that is, constantly asking) the text field to find out if the state changed. Instead you register your class with the text field and it will be the text field the one that will let you know when something happened. That will be thanks to the methods that you implemented.
Further reading: protocols and delegates
Hope this helps!
Have you made sure that testButton has its IBOutlet set before you hide it?
If you want to button to disappear when the user begins editing the text field, try UIControlEventEditingDidBegin.
I am creating an MKAnnotationView with a detail disclosure button.
In mapView: viewForAnnotation: I just create an placeholder button.
// the right accessory view needs to be a disclosure button ready to bring up the photo
aView.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
In mapView: didSelectAnnotationView: I actually create a button to be used (with the relevant tag)
// create a button for the callout
UIButton *disclosure = [self.delegate mapController:self buttonForAnnotation:aView.annotation];
NSLog(#"DisclosureButton: %#", disclosure);
// set the button's target for when it is tapped upon
[disclosure addTarget:self.delegate action:#selector(presentAnnotationPhoto:) forControlEvents:UIControlEventTouchUpInside];
// make the button the right callout accessory view
aView.rightCalloutAccessoryView = disclosure;
In the log, the button appears to be fully instantiated as well as set with the correct tag.
This is the button creator:
/**
* returns an button for a specific annotation
*
* #param sender the map controller which is sending this method to us (its' delegate)
* #param annotation the annotation we need to create a button for
*/
- (UIButton *)mapController:(MapController *) sender
buttonForAnnotation:(id <MKAnnotation>) annotation
{
// get the annotation as a flickr photo annotation
FlickrPhotoAnnotation *fpa = (FlickrPhotoAnnotation *)annotation;
// create a disclosure button used for showing photo in callout
UIButton *disclosureButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
// associate the correct photo with the button
disclosureButton.tag = [self.photoList indexOfObject:fpa.photo];
return disclosureButton;
}
The problem comes when I select the annotation. For a few seconds when the annotation is selected and the detail disclosure button is tapped, nothing happens. However, after tapping away and back onto the annotation a few times and testing the button, it eventually works as expected.
What is going on with the strange delay? Sometimes when the button is going to work, it just appears as if the alpha is set to 0.0 until you tap on it and it appears.
Seriously one of the more odd problems I've encountered.
Before the didSelectAnnotationView delegate method is called, the map view has already prepared the callout view based on the annotation view's properties (before your changes).
So the callout you see the on the first tap is without the changes the app makes in didSelectAnnotationView. On the following taps, the callout could be based on the values set from the previous tap (this actually depends on how annotation view re-use is handled in viewForAnnotation).
It looks like the only things the code is doing in didSelectAnnotationView and buttonForAnnotation is setting the button action and tag.
I assume you're using the "tag" approach because the presentAnnotationPhoto: method needs to reference the selected annotation's properties.
You don't need to use a tag to get the selected annotation in your action method. Instead, there are a couple of better options:
Your custom action method can get the selected annotation from the map view's selectedAnnotations property. See this question for an example of how to do this.
Use the map view's own delegate method calloutAccessoryControlTapped instead of a custom action method. The delegate method passes a reference to the annotation view which contains a property pointing to its annotation (ie. view.annotation) so there's no guessing, searching, or question as to what annotation was selected. I recommend this option.
In the first option, do the addTarget in viewForAnnotation and don't bother setting the tag. You also don't need the buttonForAnnotation method. Then in the button action method, get the selected annotation from mapView.selectedAnnotations.
Currently, your action method is on self.delegate so you might have some trouble accessing the map view from that other controller. What you can do is create a local button action method in the map controller which gets the selected annotation and then calls the presentAnnotationPhoto: action method on self.delegate (except now that method can be written to accept an annotation parameter instead of being a button tap handler).
The second option is similar except you don't need to do any addTarget and in the calloutAccessoryControlTapped method, call presentAnnotationPhoto: on self.delegate.
For both options, I suggest modifying the presentAnnotationPhoto: method to accept the annotation object itself (FlickrPhotoAnnotation *) instead of the current UIButton * and in the map controller, do an addTarget on a method local to the map controller (or use calloutAccessoryControlTapped) and from that method, manually call presentAnnotationPhoto: and pass it the annotation.
Suppose I have a Storyboard containing a view that contains a button. When the user presses this button, a popover comes up.
Thus, I need to set an anchor by dragging the segue to the button using Xcode (and then do performSegueWithIdentifier:).
So, my question is: is there a way to set this "anchor" programmatically?
Thank you.
In my case I've added programmatically several UIBarButtonItem.
The problem of only using an invisible view as an archor is that, if like in my case, the size of the UIBarButtonItem is changing it's size, the arrow of the popover doesnt appear centered, and althought it works, looks a bit strange.
How to solve it.
Create a small view in storyboard ( the size doesnt really matter ), make it invisible, and link it.
In my case this is called invisibleViewAsArchor
Connect the UIBarbutton item with the follow action.
-(IBAction) showMyPopover:(id)sender {
if([self.popoverController isPopoverVisible])
{
[self.popoverController dismissPopoverAnimated:YES];
}else{
self.invisibleViewAsArchor.frame = CGRectMake([sender view].frame.origin.x,
[sender view].frame.origin.y-50,
[sender view].frame.size.width,
[sender view].frame.size.height);
[self performSegueWithIdentifier:#"segue_to_something" sender:self];
}
}
as you can see before it shows the popover (with performSegueWithIdentifier), I'm changing the frame
of the Archor with the values from the button that has fired the event.
Hope it helps.
In the storyboard anchor the popover to some arbitrary button. Don't worry too much about which one as it will get overridden in the code.
In the view controller method prepareForSegue, add the code:
let dest = segue.destinationViewController
dest.popoverPresentationController?.barButtonItem = <your bar button here>
or if you want to anchor to a view instead
dest.popoverPresentationController?.barButtonItem = nil
dest.popoverPresentationController?.sourceView = <your view here>
You can't programmatically create segue's as explained here: Creating a segue programmatically, however, you can configure which destination controller you want to display at run-time. This is explained in the apple documentation here: Configuring the Destination Controller When a Segue is Triggered.
Hopefully this helps!
I had the same problem where I was creating a BarButtonItem programmatically. You may also be able to get around it by creating an invisible, disabled button which you can set as the anchor in IB.
I must be doing stupid, but I can't see what: my UITableViewController subclass is never called when the edit button of my navigation is pressed.
What could be causing that?
My view hierarchy is loaded from a Nib file and put inside a popover. The [+] button is connected to the insertNewObject action of my UITableViewController subclass. It works fine.
The [Edit] button however has no action to connect to. The doc says it will automatically call the setEditing:animated: method of the view controller, which I override.
The nib file is set up pretty much as usual AFAICT. And in fact, I'm not sure what additional detail I can give that would suggest my mistake.
What is the control flow from the click on the [Edit] button to the call of the setEditing:animated method?
I feel like we must be missing the same thing.
Whatever the case, I made it work by doing the following.
IBOutlet UIBarButtonItem *editButton;
-(IBAction)editButtonPressed:(id)sender {
[self setEditing:YES animated:YES];
}
- (void)setEditing:(BOOL)editing animated:(BOOL)animate
{
if(self.tableView.isEditing)
{
self.editButton.style = UIBarButtonItemStylePlain;
self.editButton.title = #"Edit";
}
else
{
//self.editButton.style = UIBarButtonSystemItemDone;
self.editButton.style = UIBarButtonSystemItemEdit;
self.editButton.title = #"Done";
}
// Toggle table view state
[super setEditing:!self.tableView.isEditing animated:animate];
}
I hooked the editButton up to the button I added to the nav bar and it's action to the editButtonPressed IBAction. After doing that my setEditing: is called (obviously) and the super call toggles the table view's editing state.
I'd like to use the system defined button styles, but the appropriate one is commented out because while it did change style I couldn't figure out how to change the text from "Edit" to "Done" so I had to do it all manually (that only worked if I left the button as Custom and set the style generically). This has the downside of not being localized (for free), etc.