Related
I am working on an iOS app where user can email and send sms.I am using MFMailComposeViewController & MFMessageComposeViewController for sending email and SMS but sometimes Cancel and Send button appear over composer and sometime it doesn't appear at all. This issue is random and I have tried alot to figure out. Any help will be greatly appreciated.
Code
MFMailComposeViewController *mail = [[MFMailComposeViewController alloc] init];
mail.mailComposeDelegate = self;
[mail setSubject:Email_Subject];
[mail setMessageBody:Share_Message isHTML:NO];
[mail setToRecipients:#[#""]];
[self presentViewController:mail animated:YES completion:NULL];
I have implemented MFMailComposeViewControllerDelegate in my viewController in header file.
OK, I've got some traction on this finally. My problem stemmed in part from me changing nav bar colors in my app delegate (Swift):
UINavigationBar.appearance().barTintColor = UIColor.black
UIApplication.shared.statusBarStyle = .lightContent
// To get my hamburger menu white colored
UINavigationBar.appearance().tintColor = UIColor.white
Due to this, and some interaction with the Zoomed mode (see my comment above), sometimes I'd get the Cancel/Send buttons set to white color in my mail controller. Like this:
I'll show you my class (Objective C) that displays the mail controller, which now works for me to solve the problem in this question. Note that the line:
[UINavigationBar appearance].tintColor = appleBlueTintColor;
is really the secret sauce in all of this:
#import <MessageUI/MessageUI.h>
#interface SendEmail : MFMailComposeViewController
// Returns nil if can't send email on this device. Displays an alert before returning in this case.
- (id) initWithParentViewController: (UIViewController *) parent;
// Show the email composer.
- (void) show;
// Message to append to the bottom of support emails. The string doesn't contain HTML.
+ (NSString *) getVersionDetailsFor: (NSString *) appName;
#end
#interface SendEmail ()<MFMailComposeViewControllerDelegate>
#property (nonatomic, weak) UIViewController *myParent;
#property (nonatomic, strong) UIColor *previousBarTintColor;
#property (nonatomic, strong) UIColor *previousTintColor;
#end
#import "SendEmail.h"
#import <sys/utsname.h>
#implementation SendEmail
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
// Using this init method depends on the `show` method being called immediately after-- to reset the nav bar colors back to what they were originally.
- (id) initWithParentViewController: (UIViewController *) theParent {
if (![MFMailComposeViewController canSendMail]) {
NSString *title = #"Sorry, the app isn't allowed to send email.";
NSString *message = #"Have you configured this device to send email?";
UIAlertController *alert = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:#"OK" style:UIAlertActionStyleDefault handler:nil]];
[theParent presentViewController:alert animated:YES completion:nil];
return nil;
}
// The default app nav bar color is black-- and that looks bad on the mail VC. Need to change and then set back both the barTintColor and the tintColor (in the `show` method).
self.previousBarTintColor = [UINavigationBar appearance].barTintColor;
self.previousTintColor = [UINavigationBar appearance].tintColor;
[[UINavigationBar appearance] setBarTintColor:[UIColor whiteColor]];
// I got this color from the digital color meter-- this is what it is supposed to be on the Cancel/Send buttons.
UIColor *appleBlueTintColor = [UIColor colorWithRed:31.0/255.0 green:134.0/255.0 blue:254.0/255.0 alpha:1.0];
[UINavigationBar appearance].tintColor = appleBlueTintColor;
self = [super init];
if (self) {
self.mailComposeDelegate = self;
self.myParent = theParent;
}
return self;
}
- (void) show {
[self.myParent presentViewController:self animated:YES completion:^{
[[UINavigationBar appearance] setBarTintColor: self.previousBarTintColor];
[UINavigationBar appearance].tintColor = self.previousTintColor;
}];
}
- (void)mailComposeController:(MFMailComposeViewController*)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError*)error {
[self.myParent dismissViewControllerAnimated:YES completion:nil];
}
+ (NSString *) getVersionDetailsFor: (NSString *) appName;
{
NSMutableString *message = [NSMutableString string];
[message appendString:#"\n\n\n--------------\n"];
[message appendFormat:#"iOS version: %#\n", [[UIDevice currentDevice] systemVersion]];
[message appendFormat:#"%# version: %# (%#)\n", appName,
[[NSBundle mainBundle] objectForInfoDictionaryKey:#"CFBundleShortVersionString"],
[[NSBundle mainBundle]objectForInfoDictionaryKey:#"CFBundleVersion"]];
[message appendFormat:#"Hardware: %#\n", [self machineName]];
return message;
}
// See http://stackoverflow.com/questions/11197509/ios-iphone-get-device-model-and-make
+ (NSString *) machineName;
{
struct utsname systemInfo;
uname(&systemInfo);
return [NSString stringWithCString:systemInfo.machine
encoding:NSUTF8StringEncoding];
}
#end
My problem was the below line. Just comment this line as it makes the Barbutton title colour Clear.
UIBarButtonItem.appearance().setTitleTextAttributes([NSAttributedStringKey.foregroundColor: UIColor.clear], for: .normal)
//comment this line
I am trying to replicate Facebook Messenger App, where there is a UITextView attached to the top of the keyboard.
Due to the nature of this app I need my view to be attached, instead of manually scrolling up and down a ScrollView when the keyboard appears.
This can be achieved by using a inputAccessoryView.
I read the docs on it here.
The documentation is very brief and says:
"This property is typically used to attach an accessory view to the system-supplied keyboard that is presented for UITextField and UITextView objects.
The value of this read-only property is nil. If you want to attach custom controls to a system-supplied input view (such as the system keyboard) or to a custom input view (one you provide in the inputView property), redeclare this property as read-write in a UIResponder subclass.
You can then use this property to manage a custom accessory view. When the receiver becomes the first responder, the responder infrastructure attaches the accessory view to the appropriate input view before displaying it."
I have tried declaring a property
#interface CommentViewController ()
#property (nonatomic, readwrite, retain) UIView *inputAccessoryView;
#end
And then setting it:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
UIView *view = [[UIView alloc]initWithFrame:CGRectMake(0, 20, 320, 100)];
[view setBackgroundColor:[UIColor greenColor]];
self.inputAccessoryView = view;
}
Then I have tried calling both of these:
[self.tableView becomeFirstResponder];
[view becomeFirstResponder];
Nothing happens. What am I doing wrong?
*Note - Extra information: I am using a UITableViewController that I want to have a UIView attached as an inputAccessoryView. Once I get the view working then I will add in a UITextView and more, but this is mainly an example.
Any help is greatly appreciated!
Add input accessory to your textField or textView rather than to pure UIView.
self.mytextField.inputAccessoryView = view;
The inputAccessoryView is a property of the UIResponder class. It allows you to define a custom input accessory view to display when the receiver becomes the first responder. Usually an instance of UIToolBar should be set as the accessory view.
A toolbar sample:
MYInputAccessoryToolbar.h
typedef void (^MYInputAccessoryToolbarDidDoneTap)(id activeItem);
#interface MYInputAccessoryToolbar : UIToolbar
#property (nonatomic, copy) MYInputAccessoryToolbarDidDoneTap didDoneTapBlock;
+ (instancetype)toolbarWithInputItems:(NSArray *)items;
- (instancetype)initWithInputItems:(NSArray *)items;
- (void)addInputItem:(id)item;
- (void)goToNextItem;
- (void)goToPrevItem;
#end
MYInputAccessoryToolbar.m
#interface MYInputAccessoryToolbar ()
#property (strong, nonatomic) UIBarButtonItem *nextButton;
#property (strong, nonatomic) UIBarButtonItem *prevButton;
#property (strong, nonatomic) UIBarButtonItem *doneButton;
#property (nonatomic, copy) NSMutableArray *inputItems;
#property (nonatomic) NSInteger activeItemIndex;
#property (nonatomic) id activeItem;
#end
#implementation MYInputAccessoryToolbar
+ (instancetype)toolbarWithInputItems:(NSArray *)items {
return [[self alloc] initWithInputItems:items];
}
#pragma mark - Initializations
- (instancetype)init {
self = [super init];
if (self) {
_inputItems = [NSMutableArray new];
_prevButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:101 target:self action:#selector(prevButtonTaped)];
_nextButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:102 target:self action:#selector(nextButtonTaped)];
_doneButton = [[UIBarButtonItem alloc] initWithTitle:#"Done" style:UIBarButtonItemStyleBordered target:self action:#selector(doneButtonTaped)];
[_doneButton setTitleTextAttributes:#{NSFontAttributeName:[UIFont boldSystemFontOfSize:17]} forState:UIControlStateNormal];
UIBarButtonItem *fixedSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
fixedSpace.width = 20.0f;
UIBarButtonItem *flexSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
NSArray<UIBarButtonItem *> *barButtons = #[_prevButton, fixedSpace, _nextButton, flexSpace, _doneButton];
[self sizeToFit];
self.items = barButtons;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(itemDidBeginEditing:)
name:UITextFieldTextDidBeginEditingNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(itemDidBeginEditing:)
name:UITextViewTextDidBeginEditingNotification
object:nil];
}
return self;
}
- (instancetype) initWithInputItems:(NSArray *)items {
self = [self init];
for (id item in items) {
[self addInputItem:item];
}
return self;
}
#pragma mark - Accessors
- (void)addInputItem:(id)item {
if ([item respondsToSelector:#selector(setInputAccessoryView:)]) {
[item setInputAccessoryView:self];
}
[_inputItems addObject:item];
}
#pragma mark - Actions
- (void)itemDidBeginEditing:(NSNotification *)noticifation {
NSInteger itemIndex = [_inputItems indexOfObject:noticifation.object];
if (itemIndex != NSNotFound && _activeItem != noticifation.object) {
_activeItemIndex = itemIndex;
_activeItem = noticifation.object;
[self activeItemChanged];
}
}
- (void)activeItemChanged {
_prevButton.enabled = _activeItemIndex != 0;
_nextButton.enabled = _activeItemIndex != _inputItems.count - 1;
}
- (void)prevButtonTaped {
[self goToPrevItem];
}
- (void)nextButtonTaped {
[self goToNextItem];
}
- (void)goToNextItem {
[_inputItems[_activeItemIndex + 1] becomeFirstResponder];
}
- (void)goToPrevItem {
[_inputItems[_activeItemIndex - 1] becomeFirstResponder];
}
- (void)doneButtonTaped {
if (_didDoneTapBlock) {
_didDoneTapBlock(_activeItem);
}
[_activeItem resignFirstResponder];
}
#pragma mark - Dealloc
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self name:UITextFieldTextDidBeginEditingNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UITextViewTextDidBeginEditingNotification object:nil];
}
#end
Now assuming that we have a set of text field fields and text views we could use them to initialize an instance of our toolbar.
MYInputAccessoryToolbar *accessoryToolbar = [MYInputAccessoryToolbar toolbarWithInputItems:#[_passwordCurrentField, _passwordNewField, _passwordVerifyField]];
And then each of these fields will have a custom accessory view like this.
Remove self.inputAccessoryView = view; and then add the code below anywhere after -(void)viewDidLoad { ... } where view is your UIView:
-(void) viewDidLoad {
....
}
- (UIView *)inputAccessoryView
{
return self.view;
}
I am posting this answer to show other people my exact code and how easy it actually was, however all the credit goes to MadNik.
In your view controller class where you want a keyboard, in the implementation add the following:
#implementation CommentViewController {
UIView *toolbar;
UITextView *commentTextView;
UIButton *postComment;
}
The toolbar is the actual view that gets docked to your keyboard, and the rest of the objects go on top of the view.
Next it is as simple as initiating the toolbar and setting its frame:
toolbar = [[UIView alloc]initWithFrame:CGRectMake(0, self.view.frame.size.height-50, self.view.frame.size.width, 50)];
[toolbar setBackgroundColor:[UIColor whiteColor]];
I make the frame of the toolbar initially sit right at the bottom of the view controller.
Next I just initiate the rest of the objects I want on my toolbar, e.g the UITextField and a UIButton. Just lay them out how you want:
commentTextView = [[UITextView alloc]initWithFrame:CGRectMake(8, 8, self.view.frame.size.width - 16 - 75, 34)];
[commentTextView setBackgroundColor:[UIColor colorWithWhite:0.97 alpha:1]];
commentTextView.layer.cornerRadius = 5;
[commentTextView setFont:[UIFont fontWithName:#"Avenir Next" size:20]];
[commentTextView setTextColor:[UIColor colorWithWhite:0.35 alpha:1]];
postComment = [[UIButton alloc]initWithFrame:CGRectMake(self.view.frame.size.width-75, 0, 75, 50)];
[postComment setTitle:#"Post" forState:UIControlStateNormal];
[postComment.titleLabel setFont:[UIFont fontWithName:#"Avenir Next" size:20]];
[postComment setTitleColor:[UIColor colorWithRed:(255/255.0) green:(40/255.0) blue:(80/255.0) alpha:1.0] forState:UIControlStateNormal];
Next add your objects to your tool bar:
[toolbar addSubview:commentTextView];
[toolbar addSubview:postComment];
Now this is where the magic happens: You simply set your UITextView's inputAccessoryView to whatever view you want to be docked to the keyboard.
In this case it is toolbar, because the toolbar is acting as a dock that holds everything else.
Now all you need to do is add your toolbar to your view controller, and when you tap the UITextView, since its inputAccessoryView it the toolbar, the toolbar will be docked to the keyboard!
Since I am using a UITableViewController, I had to add my toolbar to the window:
[[[UIApplication sharedApplication]delegate].window addSubview:toolbar];
So simple! No extra classes or anything needs to be made!
I need to call some methods from one view to another, and I can't seem to get it to work. What am I doing wrong?
Here's my ViewController.h
#import <UIKit/UIKit.h>
#import "mySettings.h"
#protocol ViewControllerDelegate <NSObject>
#required
- (void) getDefaults;
- (void) updateSubTotal: ( float ) value;
#end
#interface ViewController : UIViewController <UIPickerViewDataSource, UIPickerViewDelegate> {
id <ViewControllerDelegate> delegate;
}
#property (retain) id delegate;
.
. (other declarations)
.
#end
ViewController.m
#import "ViewController.h"
#import "mySettings.h"
#interface ViewController ( )
#property (assign) mySettings *settingsVC;
#end
#implementation ViewController
- ( void ) viewDidLoad {
[ super viewDidLoad ] ;
// Do any additional setup after loading the view, typically from a nib.
self.userDefaults = [[NSUserDefaults alloc] initWithSuiteName:#"group.Just-The-Tip"] ;
[ self getDefaults ] ;
self.arrPercent = # [ #"1",#"2",#"3",#"4",#"5",#"6",#"7",#"8",#"9",#"10",#"11",#"12",#"13",#"14",#"15",#"16",#"17",#"18",#"19",#"20",#"21",#"22",#"23",#"24",#"25",#"26",#"27",#"28",#"29",#"30",#"31",#"32",#"33",#"34",#"35",#"36",#"37",#"38",#"39",#"40",#"41",#"42",#"43",#"44",#"45",#"46",#"47",#"48",#"49",#"50" ] ;
self.arrPeople = # [ #"1",#"2",#"3",#"4",#"5",#"6",#"7",#"8",#"9",#"10",#"11",#"12",#"13",#"14",#"15",#"16",#"17",#"18",#"19",#"20" ] ;
self.myPicker.dataSource = self;
self.myPicker.delegate = self;
if ( self.bRememberLastBill )
{
self.subtotal = [ self.userDefaults floatForKey:#"sub_total" ] ;
self.strSubTotal = [ NSString stringWithFormat: #"%.2f", self.subtotal ] ;
[ self updateSubTotal:-3 ] ;
}
// BEGIN ENABLE DONE BUTTON FOR NUMPAD
UIToolbar * keyboardDoneButtonView = [[UIToolbar alloc]initWithFrame:CGRectMake(0, 0, 320, 32)];;
keyboardDoneButtonView.items = [NSArray arrayWithObjects:
[[UIBarButtonItem alloc] initWithTitle:#"Clear" style:UIBarButtonItemStyleDone target:self action:#selector(clearClicked:)],
[[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil],
[[UIBarButtonItem alloc] initWithTitle:#"Done" style:UIBarButtonItemStyleDone target:self action:#selector(doneClicked:)], nil
];
[keyboardDoneButtonView sizeToFit];
self.field_SubTotal.inputAccessoryView = keyboardDoneButtonView;
// END ENABLE DONE BUTTON FOR NUMPAD
UIView *paddingView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 16, 60)];
self.field_SubTotal.rightView = paddingView;
self.field_SubTotal.rightViewMode = UITextFieldViewModeAlways;
self.settingsVC = [[mySettings alloc] init];
self.settingsVC.delegate = self ;
[ self animate ] ;
}
- ( void ) getDefaults
{
// lots of stuff here
}
- ( void ) updateSubTotal: ( float ) value
{
// even more code here
}
mySettings.h
#import <UIKit/UIKit.h>
#import "ViewController.h"
#interface mySettings : UIViewController {
}
#property (nonatomic, assign) id delegate;
.
. (a bunch of declarations)
.
#end
mySettings.m
#import "mySettings.h"
#import "ViewController.h"
#interface mySettings ( )
#end
#implementation mySettings
#synthesize delegate;
- ( void ) viewDidLoad {
[ super viewDidLoad ] ;
// Do any additional setup after loading the view.
//self.userDefaults = [ NSUserDefaults standardUserDefaults ] ;
self.userDefaults = [[NSUserDefaults alloc] initWithSuiteName:#"group.Just-The-Tip"] ;
[ self getDefaults ] ;
self.textDefaultTax.text = [ NSString stringWithFormat:#"%.3f", self.default_tax ] ;
self.textDefaultTip.text = [ NSString stringWithFormat:#"%.f", self.default_tip ] ;
// BEGIN ENABLE DONE BUTTON FOR NUMPAD
UIToolbar * keyboardDoneButtonView = [[UIToolbar alloc] init];
[keyboardDoneButtonView setItems:[NSArray arrayWithObjects:
[[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil],
[[UIBarButtonItem alloc] initWithTitle:#"Done" style:UIBarButtonItemStyleDone target:self action:#selector(doneClicked:)],
nil]];
[keyboardDoneButtonView sizeToFit];
self.textDefaultTip.inputAccessoryView = keyboardDoneButtonView;
self.textDefaultTax.inputAccessoryView = keyboardDoneButtonView;
// END ENABLE DONE BUTTON FOR NUMPAD
[ self animate ] ;
}
// a few methods later
- (IBAction)up_default_exclude_tax:(id)sender {
self.bExcludeTax = self.switchExcludeTax.isOn;
[ self setDefaults ] ;
[ self.delegate getDefaults ];
[ self.delegate updateSubTotal:-3 ];
}
Frankly, you have almost confused me :). The way you are implementing delegate design pattern and connecting view controllers is not the suggested/documented way of doing it.
Lets proceed step by step.
Step 1: MySettings (like UITableViewController) implements some feature and relies on its delegate to implement others.
So, following points to take away from here:
protocol definition must be MySettingsDelegate and not ViewControllerDelegate.
rename mySettings to MySettingsViewController.
MySettingsViewController should not import ViewController. It must use its delegate property to interact with ViewController.
This is how my MySettingsViewController would look like post these changes.
MySettingsViewController.h
`
#protocol MySettingsViewControllerDelegate <NSObject>
#required
- (void) getDefaults;
- (void) updateSubTotal: ( float ) value;
#end
#interface MySettingsViewController : UIViewController {
}
#property (nonatomic, assign) id delegate;
.
. (a bunch of declarations)
.
#end`
Step2: You don't need to call #synthesis on properties any more. Also, before calling a method on delegate it is always good practice to do a nil check. Please see how my MySettingsViewController.m would look like. I have also added some NSLog statement for you to verify if controls comes there or not.
MySettingsViewController.m
#implementation MySettingsViewController
- ( void ) viewDidLoad {
[ super viewDidLoad ] ;
// Do any additional setup after loading the view.
//self.userDefaults = [ NSUserDefaults standardUserDefaults ] ;
self.userDefaults = [[NSUserDefaults alloc] initWithSuiteName:#"group.Just-The-Tip"] ;
[ self getDefaults ] ;
self.textDefaultTax.text = [ NSString stringWithFormat:#"%.3f", self.default_tax ] ;
self.textDefaultTip.text = [ NSString stringWithFormat:#"%.f", self.default_tip ] ;
// BEGIN ENABLE DONE BUTTON FOR NUMPAD
UIToolbar * keyboardDoneButtonView = [[UIToolbar alloc] init];
[keyboardDoneButtonView setItems:[NSArray arrayWithObjects:
[[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil],
[[UIBarButtonItem alloc] initWithTitle:#"Done" style:UIBarButtonItemStyleDone target:self action:#selector(doneClicked:)],
nil]];
[keyboardDoneButtonView sizeToFit];
self.textDefaultTip.inputAccessoryView = keyboardDoneButtonView;
self.textDefaultTax.inputAccessoryView = keyboardDoneButtonView;
// END ENABLE DONE BUTTON FOR NUMPAD
[ self animate ] ;
}
// a few methods later
- (IBAction)up_default_exclude_tax:(id)sender {
self.bExcludeTax = self.switchExcludeTax.isOn;
[self setDefaults];
NSLog(#"Action taken");
if (self.delegate && [self.delegate respondsToSelector:#selector(getDefaults)]) {
NSLog(#"Calling getDefaults");
[self.delegate getDefaults];
}
if (self.delegate && [self.delegate respondsToSelector:#selector(updateSubTotal:)]) {
NSLog(#"Calling updateSubTotal:");
[self.delegate updateSubTotal:-3];
}
}
Step 3: Now, that my support class ready, lets use it. Time to write ViewController class. Not sure why you added delegate in header of ViewController as well, we literally do not need it here. This is how my ViewController.h will look like:
ViewController.h
#interface ViewController : UIViewController <UIPickerViewDataSource, UIPickerViewDelegate, MySettingsViewControllerDelegate> {
}
.
. (other declarations)
.
#end
Steps 4: Now comes the main part. Please ensure you hold a strong reference to MySettingsViewController. This is how my ViewController.m would look like:
ViewController.m
#interface ViewController()
#property (nonatomic, strong) MySettingsViewController *settingsVC;
#end
- ( void ) viewDidLoad {
[super viewDidLoad ] ;
// Do any additional setup after loading the view, typically from a nib.
self.userDefaults = [[NSUserDefaults alloc] initWithSuiteName:#"group.Just-The-Tip"] ;
[ self getDefaults ] ;
self.arrPercent = # [ #"1",#"2",#"3",#"4",#"5",#"6",#"7",#"8",#"9",#"10",#"11",#"12",#"13",#"14",#"15",#"16",#"17",#"18",#"19",#"20",#"21",#"22",#"23",#"24",#"25",#"26",#"27",#"28",#"29",#"30",#"31",#"32",#"33",#"34",#"35",#"36",#"37",#"38",#"39",#"40",#"41",#"42",#"43",#"44",#"45",#"46",#"47",#"48",#"49",#"50" ] ;
self.arrPeople = # [ #"1",#"2",#"3",#"4",#"5",#"6",#"7",#"8",#"9",#"10",#"11",#"12",#"13",#"14",#"15",#"16",#"17",#"18",#"19",#"20" ] ;
self.myPicker.dataSource = self;
self.myPicker.delegate = self;
if ( self.bRememberLastBill )
{
self.subtotal = [ self.userDefaults floatForKey:#"sub_total" ] ;
self.strSubTotal = [ NSString stringWithFormat: #"%.2f", self.subtotal ] ;
[ self updateSubTotal:-3 ] ;
}
// BEGIN ENABLE DONE BUTTON FOR NUMPAD
UIToolbar * keyboardDoneButtonView = [[UIToolbar alloc]initWithFrame:CGRectMake(0, 0, 320, 32)];;
keyboardDoneButtonView.items = [NSArray arrayWithObjects:
[[UIBarButtonItem alloc] initWithTitle:#"Clear" style:UIBarButtonItemStyleDone target:self action:#selector(clearClicked:)],
[[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil],
[[UIBarButtonItem alloc] initWithTitle:#"Done" style:UIBarButtonItemStyleDone target:self action:#selector(doneClicked:)], nil
];
[keyboardDoneButtonView sizeToFit];
self.field_SubTotal.inputAccessoryView = keyboardDoneButtonView;
// END ENABLE DONE BUTTON FOR NUMPAD
UIView *paddingView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 16, 60)];
self.field_SubTotal.rightView = paddingView;
self.field_SubTotal.rightViewMode = UITextFieldViewModeAlways;
self.settingsVC = [[MySettingsViewController alloc] init];
self.settingsVC.delegate = self ;
[ self animate ] ;
}
- ( void ) getDefaults
{
// lots of stuff here
}
- ( void ) updateSubTotal: ( float ) value
{
// even more code here
}
I believe this must solve your issue and would bring clarity to delegate pattern. You also need to ensure that while MySettingsViewController is active and calls its delegate, ViewController also stays in memory.
Edit: If you are using storyboard
Please ensure you are setting the delegate correctly on the right object. Do not instantiate your own object.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"SegueRegistrationUserAction"]) {
[(MySettingsViewController *)segue.destinationViewController setDelegate:self];
}
}
Final Edit (Fixed in Code):
So, I took a look at your code and found the issue. It was apparently different objects issue. You are using storyboard and also creating a mySettings object in code as well. When you connect via storyboard, you should use the object created from storyboard and avoid creating your own. When I changed the below method in your ViewController.m, it fixed the issue.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
self.settingsVC = (mySettings *)segue.destinationViewController;
self.settingsVC.delegate = self;
}
Assuming you want to send data from your ViewController to your Settings via the delegate pattern.In your Settings.h:
#import <UIKit/UIKit.h>
#protocol SettingsDelegate
#required
- (void) getDefaults;
- (void) updateSubTotal: ( float ) value;
#end
#interface Settings : UIViewController
#property (weak, nonatomic) id<ViewControllerDelegate> delegate;
#end
Then in your ViewController.h
#import "Settings.h"
#interface Settings : UIViewController<ViewControllerDelegate, UIPickerViewDataSource, UIPickerViewDelegate>
#end
This basically says that your ViewController is going to contain the delegate methods that you defined in the SettingsDelegate protocol.
So in your ViewController.m:
#interface ViewController ()
#end
#implementation ViewController
- (void) viewDidLoad{
viewController.delegate = self; //somehow get a copy of your viewController instance and set the delegate. This doesn't have to be in viewDidLoad, but it needs to happen sometime.
}
- (void) getDefaults{
//Do stuff. You probably want to change the return type to something other than void if we are actually getting defaults here.
}
- (void) updateSubTotal: ( float ) value{
//Do stuff.
}
...
#end
Then in your Settings.m, when the appropriate time comes. you can call:
[self.delegate getDefaults];
Hi I am adding one custom button programmatically on navigation bar and i have written all button properties in my background class and i am calling this method from my main class
And here I have used protocols for getting button touching event in my main class from background class but it's not working using protocols
my code:-
background class:-
BackGroundClass.h
#import <UIKit/UIKit.h>
#protocol buttonprotocol<NSObject>
#required
- (void) buttonTapped;
#end
#interface BackGroundClass : UIViewController
#property (nonatomic, weak) id<buttonprotocol> delegate;
#end
BackGroundClass.m
#import "BackGroundClass.h"
#interface BackGroundClass ()
#end
#implementation BackGroundClass
#synthesize delegate;
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (void)backbutton: (UINavigationItem *)navigationitem
{
UIImage* image3 = [UIImage imageNamed:#"back.png"];
CGRect frameimg = CGRectMake(15,5,25,25);
UIButton *someButton = [[UIButton alloc] initWithFrame:frameimg];
[someButton setBackgroundImage:image3 forState:UIControlStateNormal];
[someButton addTarget:self action:#selector(Back_btn:)
forControlEvents:UIControlEventTouchUpInside];
[someButton setShowsTouchWhenHighlighted:YES];
UIBarButtonItem *mailbutton =[[UIBarButtonItem alloc] initWithCustomView:someButton];
navigationitem.leftBarButtonItem = mailbutton;
}
- (void)Back_btn :(id)sender
{
[delegate buttonTapped];
}
#end
main class:-
mainclass.h
#import <UIKit/UIKit.h>
#import "BackGroundClass.h"
#interface mainclass : UIViewController<buttonprotocol>
mainclass.m
#import "mainclass.h"
#import "BackGroundClass.h"
#interface mainclass ()
{
BackGroundClass * bg;
}
#end
#implementation mainclass
- (void)viewDidLoad {
[super viewDidLoad];
bg = [[BackGroundClass alloc]init];
[bg backbutton:self.navigationItem];
}
- (void)buttonTapped
{
NSLog(#"ok it's 2");
}
but that above button tapped a method isn't called what I did wrong?
When you are creating the object of your BackGroundClass class, then you are not setting the delegate of the class to self, thats why your delegate method is not calling, try it like this
bg = [[BackGroundClass alloc]init];
bg.delegate = self;
First of all, using Class in class names is bad practice. As for View Controllers you should use either ViewController or VC postfix for your class names.
For example, the proper names of your Objective-C classes would be: BackgroundViewController, MainViewController
Secondly, ViewController instances are used to react to user interaction with the view attached to this ViewController and provide visible changes based on Data component of MVC work.
So, there is no reason to use second, in your case BackgroundViewController (BackgroundClass).
For more info about MVC pattern, please refer this link:
Apple's MVC articles
As there is no need in external ViewController we also should remove the delegate using and place all logic inside this main View Controller.
Thirdly, as mentioned in Apple's Mobile HIG, buttons should have a height around 44px, so your back button's size of 25x25 would make most users experience hard trying to tap on it.
At last, you should receive the class with code similar to it:
MainViewController.h
#import <UIKit/UIKit.h>
#interface MainViewController : UIViewController
#end
MainViewController.m
#import "MainViewController.h"
#implementation MainViewController
- (void)viewDidLoad {
[super viewDidLoad];
UIImage *icon = [UIImage imageNamed:#"back.png"];
static CGFloat defaultButtonSize = 44.; // Default button's tap area size
CGSize buttonSize = CGSizeMake(defaultButtonSize, defaultButtonSize);
// The size image should take inside the button
CGSize imageSizeInsideButton = CGSizeMake(25., 25.);
CGRect frameimg = CGRectMake(15., 5., defaultButtonSize, defaultButtonSize);
UIButton *someButton = [[UIButton alloc] initWithFrame:frameimg];
[someButton setImage:icon forState:UIControlStateNormal];
[someButton addTarget:self
// No need to put colon after method name,
// since you have no use of control that triggered action
action:#selector(backButtonClicked)
forControlEvents:UIControlEventTouchUpInside];
someButton.showsTouchWhenHighlighted = YES;
// Calculate edge insets so image will take the size
// you specified dozen of lines before
CGFloat imageVerticalPaddingInButton = (buttonSize.height - imageSizeInsideButton.height) / 2.;
CGFloat imageHorizontalPaddingInButton = (buttonSize.height - imageSizeInsideButton.height) / 2.;
// Apply it to button
someButton.imageEdgeInsets = UIEdgeInsetsMake(imageVerticalPaddingInButton,
imageHorizontalPaddingInButton,
imageVerticalPaddingInButton,
imageHorizontalPaddingInButton);
UIBarButtonItem *mailbutton =[[UIBarButtonItem alloc] initWithCustomView:someButton];
self.navigationItem.leftBarButtonItem = mailbutton;
}
// As stated in "addTarget", there is no "sender" since you don't really need it
// If you ever would need the use of it,
// don't forget to put colon in "addTarget" UIButton's method
// and place something like :(UIButton *)buttonThatClicked
// after methodName
- (void)backButtonClicked {
// Get back to your previous view controller or do whatever you like there
[self.navigationController popViewControllerAnimated:YES];
}
#end
But if you want to use this thing in as many View Controllers as possible, then you should create the category:
UIViewController+CustomBackButton.h
#import <UIKit/UIKit.h>
#interface UIViewController (CustomBackButton)
- (void)setBackButtonImage:(UIImage *)image withSize:(CGSize)size target:(id)target selector:(SEL)selector;
#end
UIViewController+CustomBackButton.m
#import "UIViewController+CustomBackButton.h"
#implementation UIViewController (CustomBackButton)
- (void)setBackButtonImage:(UIImage *)image withSize:(CGSize)size target:(id)target selector:(SEL)selector {
static CGFloat defaultButtonSize = 44.;
CGSize buttonSize = CGSizeMake(defaultButtonSize, defaultButtonSize);
CGRect frameimg = CGRectMake(15., 5., defaultButtonSize, defaultButtonSize);
UIButton *someButton = [[UIButton alloc] initWithFrame:frameimg];
[someButton setImage:image forState:UIControlStateNormal];
[someButton addTarget:target
action:selector
forControlEvents:UIControlEventTouchUpInside];
someButton.showsTouchWhenHighlighted = YES;
CGFloat imageVerticalPaddingInButton = (buttonSize.height - size.height) / 2.;
CGFloat imageHorizontalPaddingInButton = (buttonSize.height - size.height) / 2.;
someButton.imageEdgeInsets = UIEdgeInsetsMake(imageVerticalPaddingInButton,
imageHorizontalPaddingInButton,
imageVerticalPaddingInButton,
imageHorizontalPaddingInButton);
UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithCustomView:someButton];
self.navigationItem.leftBarButtonItem = backButton;
}
#end
And then your View Controller's implementation file will be much cleaner.
MainViewController.m
#import "MainViewController.h"
#import "UIViewController+CustomBackButton.h"
#implementation MainViewController
- (void)viewDidLoad {
[super viewDidLoad];
UIImage *icon = [UIImage imageNamed:#"back.png"];
CGSize iconSize = CGSizeMake(25., 25.);
[self setBackButtonImage:icon
withSize:iconSize
target:self
selector:#selector(backButtonClicked)];
}
- (void)backButtonClicked {
// Get back to your previous view controller or do whatever you like there
[self.navigationController popViewControllerAnimated:YES];
}
#end
And then you can use this on every ViewController to customize your back button.
I want to have a persistent button in the bottom right corner of my app. During all view transitions, the button should remain static. I'm having trouble deciding what view to add the button to. I know the button ought to be stored in the AppDelegate, but I don't know what other view it would be sense to add it to except the window. One downside of adding it to the window is that when there's an app running in the background (ie Phone), the added status bar padding will push down the window. In general, adding it to the window seems to be a hacky solution -- any thoughts?
Yes, adding it to the UIWindow would be extremely hacky and finicky.
Storyboards
If you're using Storyboards and iOS 5.0 onwards, you should be able to use container views and do something like this:
Here's another picture showing the, rather simplistic, structure of the first View Controller:
The view controller on the left has a container, and then a view which holds the button on top of it. The container indicates that the navigation controller (directly to the right) should appear within itself, that relationship is shown by the =([])=> arrow (formally known as an embed segue). Finally the navigation controller defines its root view controller to the one on the right.
In summary, the first view controller pancakes-in the container view with the button on top, so everything that happens inside has to have the button on top.
Using childViewControllers
aka. The "I hate Storyboards and puppies" mode
Using a similar structure to the Storyboard version, you could create the base view controller with its button, and then, add the view that will become then new "root" of the application, underneath.
To make it clear, let's call the one view controller that holds the button the FakeRootViewController, and the view controller that will be, for all practical purposes, the root of the application: RootViewController. All subsequent view controllers won't even know that there's the FakeRootViewController above everyone else.
FakeRootViewController.m
// The "real" root
#import "RootViewController.h"
// Call once after the view has been set up (either through nib or coded).
- (void)setupRootViewController
{
// Instantiate what will become the new root
RootViewController *root = [[RootViewController alloc] <#initWith...#>];
// Create the Navigation Controller
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:root];
// Add its view beneath all ours (including the button we made)
[self addChildViewController:nav];
[self.view insertSubview:nav.view atIndex:0];
[nav didMoveToParentViewController:self];
}
AppDelegate.m
#import "FakeRootViewController.h"
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
FakeRootViewController *fakeRoot = [[FakeRootViewController alloc] <#initWith...#>];
self.window.rootViewController = fakeRoot;
[self.window makeKeyAndVisible];
return YES;
}
That way, you can have all the benefits of inserting the button on the window, without all the guilt and "Should I really be a programmer?" that it causes.
Potentially you could have 1 main "root" view controller, and all you other view controllers could be child view controllers, with their views as child views. Then they would have their content, and the button would be in the "root" view controller. But this seems just as sketchy and hacky as putting it in the window, and probably less convenient.
I use this button:
#interface UIPopUpButton : UIImageView <UIPopoverControllerDelegate, UIActionSheetDelegate>
{
UIPopoverController* popoverController;
Class popoverClass;
}
- (id) initWithPoint: (CGPoint) point;
- (void) touchesBegan: (NSSet*) touches
withEvent: (UIEvent*) event;
+ (id) buttonAtPoint: (CGPoint) point;
+ (id) buttonAtOriginalPoint;
+ (void) unhighlight;
+ (void) bringButtonToFront;
#property (nonatomic, retain) UIPopoverController* popoverController;
#property (nonatomic, assign) Class popoverClass;
#end
#import "UIPopUpButton.h"
#implementation UIPopUpButton
static UIPopUpButton* button = nil;
static CGPoint originalPoint;
#synthesize popoverClass;
#synthesize popoverController;
+ (id) buttonAtPoint: (CGPoint) point
{
if (button == nil)
{
button = [[UIPopUpButton alloc] initWithPoint: point];
originalPoint = point;
button.popoverClass = [UIPopoverController class];
}
else
{
button.frame = CGRectMake(point.x, point.y, button.frame.size.width, button.frame.size.height);
}
return button;
}
+ (id) buttonAtOriginalPoint
{
return [self buttonAtPoint: originalPoint];
}
+ (void) unhighlight
{
button.highlighted = NO;
}
+ (void) bringButtonToFront
{
[[UIApplication sharedApplication].keyWindow addSubview: [self buttonAtOriginalPoint]];
}
- (id) initWithPoint: (CGPoint) point
{
UIImage* image1 = [UIImage imageNamed: #"topbutton.png"];
UIImage* image2 = [UIImage imageNamed: #"topbutton.png"];
if ((self = [super initWithImage: image1
highlightedImage: image2]))
{
self.userInteractionEnabled = YES;
self.frame = CGRectMake(point.x, point.y, self.frame.size.width, self.frame.size.height);
self.multipleTouchEnabled = NO;
}
return self;
}
- (BOOL) isAppCurrStatus
{
return ([DevToolsClientController sharedInstance].statusOfRootViewController == FrontEndApplication);
}
- (void) touchesBegan: (NSSet*) touches withEvent: (UIEvent*) event
{
UITouch* touch = [touches anyObject];
if(touch.view == self)
{
if (self.popoverController == nil)
{
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
{
UIActionSheet* actionSheet = [[UIActionSheet alloc] initWithTitle: #"Please choice operation:"
delegate: self
cancelButtonTitle: nil
destructiveButtonTitle: nil
otherButtonTitles: nil];
[actionSheet addButtonWithTitle: #"Cancel"];
actionSheet.cancelButtonIndex = 0;
[actionSheet addButtonWithTitle: #"Button 1"];
actionSheet.actionSheetStyle = UIActionSheetStyleDefault;
[actionSheet setTag: 0];
[actionSheet setDelegate: self];
[actionSheet showInView: [self superview]];
[actionSheet release];
[actions release];
}
else
{
PopoverMenuController* contentViewController = [[PopoverMenuController alloc] init];
self.popoverController = [[UIPopoverController alloc] initWithContentViewController: contentViewController];
popoverController.delegate = self;
[popoverController presentPopoverFromRect: CGRectMake(10.0f, 10.0f, 5.0f, 5.0f)
inView: self
permittedArrowDirections: UIPopoverArrowDirectionAny
animated: YES];
contentViewController.popoverController = self.popoverController;
[contentViewController reloadData];
}
}
else
{
[self.popoverController dismissPopoverAnimated:YES];
self.popoverController = nil;
}
}
[super touchesBegan: touches withEvent: event];
}
#pragma mark UIActionSheetDelegate implementation
-(void) actionSheet: (UIActionSheet*) actionSheet clickedButtonAtIndex: (NSInteger) buttonIndex
{
NSNumber* indexAction = [[NSNumber alloc] initWithInt: buttonIndex - 1];
}
- (void) runAction: (NSNumber*) indexAction
{
[DevToolsPopoverMenuController runAction: [indexAction integerValue]];
}
#pragma mark -
#pragma mark UIPopoverControllerDelegate implementation
- (void) popoverControllerDidDismissPopover: (UIPopoverController*) thePopoverController
{
if (self.popoverController != nil)
{
self.popoverController = nil;
}
}
- (BOOL) popoverControllerShouldDismissPopover: (UIPopoverController*) thePopoverController
{
//The popover is automatically dismissed if you click outside it, unless you return NO here
return YES;
}
#end
call:
[UIPopUpButton bringButtonToFront];
My button is always on top.
Try subclassing the UIViewController class and make your own one with the button
Create a singleton object that holds the button so all view controllers can reference it and add it to their subview or add it to the window directly.
SomeClass.h
#property (nonatomic) UIButton *yourButton;
+(SomeClass*)sharedSomeClass;
SomeClass.m
#synthesize yourButton = _yourButton;
-(id)init
{
self = [super init];
if(self)
{
_yourButton = [UIButton new];
//Other settings you want for your button
}
return self;
}
+(SomeClass)sharedSomeClass
{
static SomeClass *sharedSomeClass;
if (!sharedSomeClass)
sharedSomeClass = [[super allocWithZone:nil]init];
return sharedSomeClass;
}
+(void)allocWithZone:(NSZone*)zone
{
return [self sharedSomeClass];
}
If you like you can access the window directly like this:
UIWindow *mainwindow = [[[UIApplication sharedApplication]delegate]window];
import SomeClass.h into your view controllers, and access the button from anywhere
#import "SomeClass.h"
SomeClass *someClass = [SomeClass sharedSomeclass];
UIButton *localButton = someClass.yourButton;