I am working on a simple app. This app has various textfields that require the 'Done' button in a toolbar.
Currently, this toolbar requires <10 lines of code, but I dislike the fact that for every textfield in every viewController I need to add those lines.
So, is there a way to shorten this 'toolbar setup'? Ideally, I would need to call a single method containing all I need:
[self addDefaultToolbar:field]
But then again adding this method everywhere I need seems a bit 'dirty' (maybe this is not clear enough, self implies that within the controller there's a method called addDefaultToolBar).
So this is what I tried:
I made a nice folder, an header and an .m file. In the header I put this:
#ifndef UIUtils_h
#define UIUtils_h
#import <UIKit/UIKit.h>
#interface UIUtils : NSObject
-(void)addDefaultToolbar:(UITextField *)field;
#end
#endif /* UIUtils_h */
And in the .m file I put this:
#import "UIUtils.h"
#implementation UIUtils
-(void)addDefaultToolbar:(UITextField *)field{
UIToolbar *textFieldToolbar = [[UIToolbar alloc] init];
[textFieldToolbar sizeToFit];
UIBarButtonItem *doneButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:#selector(doneClick)];
UIBarButtonItem *flexSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:self action:nil];
[textFieldToolbar setItems:[NSArray arrayWithObjects:flexSpace, doneButton, nil] animated:NO];
field.inputAccessoryView = textFieldToolbar;
}
-(void)doneClick
{
[[UIApplication sharedApplication] sendAction:#selector(resignFirstResponder) to:nil from:nil forEvent:nil];
}
#end
And then I call this from where I need it, like this:
#import "UIUtils.h"
UIUtils *utils = [[UIUtils alloc] init];
[utils addDefaultToolbar:field];
Now, unfortunately, the button doesn't work. The keyboard should be dismissed but it isn't. Secondly, I have no idea if I am doing this right.
How do I do this properly?
Edit:
Further research indicates that the doneClick method is not being called at all. It does work if I add doneClick to the viewController, but this kinda defeats the purpose of what I'm trying to achieve here.
What you want is a toolbar that takes care of its own button press. You can create one of those.
#interface MYDefaultToolBar : UIToolbar
#end
#implementation MYDefaultToolBar
- (instancetype)init
{
self = [self initWithFrame: CGRectZero];
if (self) {
[self sizeToFit];
UIBarButtonItem *doneButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone
target:self action:#selector(doneClick)];
UIBarButtonItem *flexSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace
target:self action:nil];
[self setItems:[NSArray arrayWithObjects:flexSpace, doneButton, nil] animated:NO];
}
return self;
}
-(void)doneClick
{
[[UIApplication sharedApplication] sendAction:#selector(resignFirstResponder) to:nil from:nil forEvent:nil];
}
#end
Then you want to be able to add it to text fields. I'd probably recommend doing that by hand:
textField.inputAccessoryView = [MYDefaultToolBar new];
This is very clear that it's modifying the inputAccessoryView (so if something else in the same code also modifies the inputAccessoryView, it'll be more obvious that there's a conflict). But you can also do this with a category, particularly if it were more complex.
#interface UITextField (AddDefaultToolBar)
- (void)my_addDefaultToolBar;
#end
#implementation UITextField (AddDefaultToolBar)
- (void)my_addDefaultToolBar {
self.inputAccessoryView = [MYDefaultToolBar new];
}
Or you could modify -[MYDefaultToolBar init] to take a text field, and add itself.
Related
I've read selector as parameter in IOS post. However, I want to extend the question.
In my case, I'm creating an Objective-C (not Swift) Master/Detail application, and want to create DetailController object of type UIViewController with the following init function
In DetailViewController.h
#interfact DetailViewController : UIViewController
- (id)initWithNibName:(NSString *)nibName withSaveSelector:(SEL)saveSelector
#end
... and in DetailViewController.m
- (id)initWithNibName:(NSString *)nibName withSaveSelector:(SEL)saveSelector
{
self = [super initWithNibName:nibName bundle:nil];
if (self) {
UIBarButtonItem *done = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemDone
target:self
action:saveSelector];
[[self navigationItem] setRightBarButtonItem:done animated:YES];
}
return self;
}
NOW, I want to just keep re-using above code to create different DetailViewControllers, and want to pass a saveSelector function that will get called when the user presses the "Done" button.
Now I have another view controller
#interface AnotherViewController : UIViewController
- (void)saveSelector:(id)sender;
#end
...and then in yet another object (i.e., NOT in AnotherViewController.m code), I use the above view controller, like so...
AnotherViewController *avc = [[AnotherViewController alloc] init];
DetailViewController *dvc = [[DetailViewController alloc] initWithNibName:#"AnotherViewController" withSaveSelector:#selector(???)];
What should I put in the ??? so I can pass AnotherViewController::saveSelector() function to DetailViewController?
I hope that makes sense.
Change your method as:
- (id)initWithNibName:(NSString *)nibName withSaveSelector:(SEL)saveSelector forTarget:(id)target
And call it like this:
DetailViewController *dvc = [[DetailViewController alloc] initWithNibName:#"AnotherViewController" withSaveSelector:#selector(saveSelector:) forTarget:avc];
In DetailViewController.m
- (id)initWithNibName:(NSString *)nibName withSaveSelector:(SEL)saveSelector forTarget:(id)target
{
self = [super initWithNibName:nibName bundle:nil];
if (self) {
UIBarButtonItem *done = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemDone
target:target
action:saveSelector];
[[self navigationItem] setRightBarButtonItem:done animated:YES];
}
return self;
}
I have a view controller named ViewController.
ViewController displays a UIView that has a button on it, which allows me to advance to a second view controller - SecondViewController.
SecondViewController also has a button on it, which allows me to advance to a third view controller.
However, I am having trouble displaying ThirdViewController. When I tap the button in SecondViewController it throws an error:
Warning: Attempt to present ... on ... whose view is not in the window hierarchy!
I have been to a bunch of other sites and posts that address this issue, but cannot seem to find a working solution. I have implemented quite a few solutions, but none seem to work.
Here is my code:
- (void)secondaryView:(UIView *)secondaryView
{
UIView *theView = secondaryView;
UIViewController *viewController = [[UIViewController alloc] init];
viewController.view.frame = [UIScreen mainScreen].bounds;
[viewController.view addSubview:theView];
viewController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self presentViewController:viewController animated:YES completion:nil];
}
secondaryView is a UIView that I am constructing elsewhere in the application. I add it to the viewController then present the viewController.
Is there any way to dynamically create UIViewControllers, add a UIView to each one, and add them to the window hierarchy?
If you can use navigation in your ViewControllers, you can try the below code
In the place where you call the firstViewController,
-(void)callFirst
{
FirstViewController *first = [FirstViewController alloc] init];
UINavigationController *navigationController = [UINavigationController alloc] initWithRootViewController:first];
navigationController.modalPresentaionStyle = UIModalPresenationFormSheet;
[self presentModalViewController:navigationController animated:YES];
}
And in the FirstViewController button action ,you can write
-(void)callSec
{
SecondViewController *sec = [SecondViewController alloc] init];
[self.NavigationController pushViewController:sec animated:YES];
}
And in SecondViewController you can do the same
-(void)callThird
{
ThirdViewController *third = [ThirdViewController alloc] init];
[self.NavigationController pushViewController:third animated:YES];
}
Try this...And in these ViewControllers you can do whatever coding you want to meet the requirement like
-(void)viewDidLoad
{
UIView *newView = [[UIView alloc] initWithFrame:CGRectMake(20,20,400,400)];
newView.backGroundColor = [UIColor redColor];
[self.view addSubView:newView];
}
I am trying to display my popover menu whenever the user clicks on a button.
I have two methods, the first creates the button
- (void)viewDidLoad {
[super viewDidLoad];
UIBarButtonItem *btnMenu = [[UIBarButtonItem alloc]initWithTitle:#"Forms List" style:UIBarButtonItemStyleBordered target:self action:#selector(showPopover:)];
self.navigationItem.leftBarButtonItem = btnMenu;
}
And the second is called by the first, to display the popover
-(IBAction)showPopover:(id)sender{
NSLog(#"called with %#", sender);
self.popoverController = popoverController;
popoverController.delegate = self;
[popoverController presentPopoverFromBarButtonItem:sender permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
}
Presently, when I click the button nothing happens. I think the problem lies in the second method, mainly the presentPopoverFromBarButtonItem line.
Any help would be appreciated, I don't really understand how that method call works.
Thanks.
EDIT 1:
Here is the code where I (think) initialize the popoverController
#interface DetailViewController ()
#property (nonatomic, retain) UIPopoverController *popoverController;
- (void)configureView;
#end
#implementation DetailViewController
#synthesize toolbar, popoverController, detailItem, detailDescriptionLabel;
Have you initialized your UIPopoverController?
UIPopoverController *popoverController = [[UIPopoverController alloc] initWithContentViewController:VCTHATGOESINSIDE];
If all else fails, try presenting it from rect and specify a CGRect. Hope this helps!
edit: initWith*Content*ViewController
So I have an issue with my rightbarbuttonitem disappearing. I have two ways of loading this view, first time launch it loads it after user enters in their name (from an initial view). Second time (after the app exits), it checks if the name exists in my stored database and if it does it loads the view right away. This second time is where the button does not show.
The button was set in viewDidLoad of my view originally here (and is still set here for the first load):
if (self.navigationItem.rightBarButtonItem == nil){
addButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:#selector(buttonPressed)];
self.navigationItem.rightBarButtonItem = addButton;
[self.navigationItem.rightBarButtonItem retain];
}
Then in the .m of my AppDelegate, I added the button to think that would resolve it on the second load:
if(success){
self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
UIViewController *control = [[ViewController alloc] initWithNibName:#"myNib" bundle:nil];
[control retain];
UINavigationController *navControl = [[UINavigationController alloc] initWithRootViewController:control];
[navControl retain];
[self.window setRootViewController:navControl];
if (navControl.navigationItem.rightBarButtonItem == nil){
addButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:#selector(buttonPressed)];
navControl.navigationItem.rightBarButtonItem = addButton;
[navControl.navigationItem.rightBarButtonItem retain];
}
//[navControl release];
[self.window makeKeyAndVisible];
return;
Here's the declaration of the addButton in the header for App Delegate and my view's header:
UIBarButtonItem *addButton;
#property (nonatomic, retain) UIBarButtonItem *addButton
Other posts say to check viewDidLoad/viewWillAppear but putting that first blurb of code in either of those does not solve the issue.
Solved it myself, since the button was never added to the nib, initializing UIViewController doesn't help. I had to initialize my view controller.
In other words replace: UIViewController *control = [[ViewController alloc] initWithNibName:#"myNib" bundle:nil]; with: MyViewControllerClassName *control = [[MyViewControllerClassName alloc] initWithNibName:#"myNib" bundle:nil]; Hope this helps others in the future, thanks for taking the time to read this if you did!
I'm getting an analyser leak, however this is the same code i'm using elsewhere without a problem. I know I'm using alloc and therefore I have to release, but I am doing this in dealloc.
What am I doing wrong ?
Header file:
#interface myViewController : UIViewController <UITableViewDataSource,
UITableViewDelegate> {
UIBarButtonItem *addButton;
}
#property (nonatomic, retain) UIBarButtonItem *addButton;
Main file:
#synthesize addButton;
- (void)viewDidLoad {
NSMutableArray* buttons = [[NSMutableArray alloc] initWithCapacity:3];
addButton = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemAdd
target:self action:#selector(btnNavAddPressed:)];
addButton.style = UIBarButtonItemStyleBordered;
[buttons addObject:addButton];
[tools setItems:buttons animated:NO];
[buttons release];
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc]
initWithCustomView:tools];
addButton.enabled = FALSE;
- (void)dealloc {
[addButton release];
when you use a property and assign to it the attributes you specified determine whether retainCount is incremented if you assign to the property. In your case you specified "retain" which means that the setter function that handles assignment to your property will automatically increment the retain count for the object.
However when you write
addButton = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemAdd
target:self action:#selector(btnNavAddPressed:)];
you are creating an opject with already retain count == 1 so when you assign it will have retain count 2. the proper way to do this is to create a temp variable and create the object, then assign the temp variable to the property thereafter releasing the temp. variable:
UIBarButtonItem* tmp = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemAdd
target:self action:#selector(btnNavAddPressed:)];
self.addButton = tmp;
[tmp release];
of course i would recommend a more descriptive name than 'temp' as variable name.
You are not using the setter, the code should be:
self.addButton = [[[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemAdd
target:self action:#selector(btnNavAddPressed:)] autorelease];
This type of problem can be avoided by using an ivar that has a different name than the property name. This is accomplished in the #synthesize statement:
#synthesize addButton = _addButton;
This way any omission of self will result in an error message.
Here is a full implementation (except tools is undefined), the property addButton is handles in all places:
#interface myViewController : UIViewController <UITableViewDataSource, UITableViewDelegate> {
}
#property (nonatomic, retain) UIBarButtonItem *addButton;
#end
#implementation myViewController
#synthesize addButton = _addButton;
- (void)viewDidLoad {
NSMutableArray* buttons = [NSMutableArray array];
self.addButton = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemAdd
target:self action:#selector(btnNavAddPressed:)];
self.addButton.style = UIBarButtonItemStyleBordered;
[buttons addObject:self.addButton];
[tools setItems:buttons animated:NO];
self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc] initWithCustomView:tools] autorelease];
self.addButton.enabled = FALSE;
}
- (void)dealloc {
[_addButton release];
}
#end
Both the above answers are misleading. You don't need to use a setter, it's perfectly fine to assign objects directly to iVars. You do need to release anything you alloc or retain however. The problem you have is here:
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:tools];
This line is alloc'ing a UIBarButtonItem instance and setting it to the rightBarButtonItem property of the navigationItem. That means the navigationItem is retaining the UIBarButtonItem and it is responsible for that retain. You are responsible for releasing it b/c of the alloc and you are not. Change the code to this:
self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc] initWithCustomView:tools] autorelease];
and this leak goes away.
You are not taking advantage of the declared property but I don't see any problem with addButton. The leak seems more in:
self.navigationItem.rightBarButtonItem =
[[UIBarButtonItem alloc] initWithCustomView:tools];
Just add autorelease and the leak will go away.