I have subclassed UIAlertView and inside which I am showing a textField which takes the input. When user clicks on textField the keyboard shows up and UIAlertView moves up to adjust for keyboard. But when I do [textField becomeFirstResponder] in didPresentAlertView delegate method of UIAlertView, the alertView doesn't moves up to adjust for keyboard. Instead the UIAlertView gets hidden behind the keyboard.
PS - I know that Apple says that UIAlertView should not be subclassed and to be used as it is, but I am subclassing UIAlertView because I want to redesign the Apple's default UI elements in it.
You should really not do something against Apple's recommendation.
Reasons
You can face unexpected issues like the one you are facing.
Your code can break for future iOS releases as you are violating recommendations.
Redesigning Apple's standard controls is in violation of HIG guidelines. As a result, there is a chance that your app can get rejected. Create your own instead, by using UIView subclass.
As an alternative, Apple has made provision in the UIAlertView for this requirement. You don't need to add a textfield to the alert view, instead, use the UIAlertView property alertViewStyle. It accepts values defined in the enum UIAlertViewStyle
typedef NS_ENUM(NSInteger, UIAlertViewStyle) {
UIAlertViewStyleDefault = 0,
UIAlertViewStyleSecureTextInput, // Secure text input
UIAlertViewStylePlainTextInput, // Plain text input
UIAlertViewStyleLoginAndPasswordInput // Two text fields, one for username and other for password
};
Example, lets assume a use case that you want to accept password from the user. The code to achieve this is as below.
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Please enter password"
message:nil
delegate:self
cancelButtonTitle:#"Cancel"
otherButtonTitles:#"Continue", nil];
[alert setAlertViewStyle:UIAlertViewStyleSecureTextInput];
[alert show];
To validate the input, lets say password entered must be minimum 6 characters, implement this delegate method,
- (BOOL)alertViewShouldEnableFirstOtherButton:(UIAlertView *)alertView
{
NSString *inputText = [[alertView textFieldAtIndex:0] text];
if( [inputText length] >= 6 )
{
return YES;
}
else
{
return NO;
}
}
To get the user input
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
NSString *title = [alertView buttonTitleAtIndex:buttonIndex];
if([title isEqualToString:#"Login"])
{
UITextField *password = [alertView textFieldAtIndex:0];
NSLog(#"Password: %#", password.text);
}
}
To re-iterate,
UIAlertView has a private view hierarchy and it is recommended to use it as-is without modification. If you use it against recommendation you will get unexpected results.
From Apple docs
The UIAlertView class is intended to be used as-is and does not support subclassing. The view hierarchy for this class is private and must not be modified.
This is standard technique used even in iOS default apps (Ex: Entering Wi-Fi password, etc.), hence using this will ensure you don't face issues like the one you mention.
Hope that helps!
I do like this to upthe screen to show the textfiled. :-) Hope this help you.
- (void) textFieldDidBeginEditing:(UITextField *)textField {
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDelegate:self];
[UIView setAnimationDuration:0.5];
[UIView setAnimationBeginsFromCurrentState:YES];
self.view.frame = CGRectMake(( self.view.frame.origin.x), (self.view.frame.origin.y-50 ), self.view.frame.size.width, self.view.frame.size.height);
[UIView commitAnimations];
}
- (void) textFieldDidEndEditing:(UITextField *)textField {
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDelegate:self];
[UIView setAnimationDuration:0.5];
[UIView setAnimationBeginsFromCurrentState:YES];
self.view.frame = CGRectMake(self.view.frame.origin.x, self.view.frame.origin.y+50 , self.view.frame.size.width, self.view.frame.size.height);
[UIView commitAnimations];
}
Related
In my UITextField,when I type # ,I am able to show a pop up containing an array values.
But now my client need to do some modifications.If a user type #, popup should not come,but when he type any letter after #,all the friends name starting with that letter should come in a popup.
Eg:- if user typed #p - pop up will come with all the friends name starting with letter P.
How to do this,I have tried something but could not make it happen
(I am getting friends list in an array while loading the view)
Now I am using
- (BOOL) textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string{
if([string isEqualToString:#"#"]) {
s=1;
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.5];
[frndsView setCenter:CGPointMake(frndsView.center.x, frndsView.center.y-310)];
[UIView commitAnimations];
[commentField resignFirstResponder];
}
return YES;
}
You mean Auto Complete. Check this tutorial.
I am creating a "sign in" and "create account" form for my iOS app. I successfully implemented the scrolling up of the UITextField when it is hidden. However, now that I implemented the "next" button the "UIKeyboardDidShowNotification" is not called because the keyboard is never dismissed. I need the keyboardWasShow method called so I can check if the active UITextField is hidden or not.
// Called when the UIKeyboardDidShowNotification is sent.
- (void)keyboardWasShown:(NSNotification*)aNotification
{
NSDictionary* info = [aNotification userInfo];
CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
// If active text field is hidden by keyboard, scroll it so it's visible
// Your application might not need or want this behavior.
CGRect aRect = self.view.frame;
CGPoint pointInSuperview = [self.view convertPoint:self.activeField.frame.origin fromView:self.scrollView];
aRect.size.height -= kbSize.height;
//added 10 to y axis because tip of origin was outside of keyboard
pointInSuperview.y +=20;
if (!CGRectContainsPoint(aRect, pointInSuperview)) {
CGPoint scrollPoint = CGPointMake(0.0, pointInSuperview.y - (kbSize.height -15));
NSLog(#"it is not in the rect");
[self.scrollView setContentOffset:scrollPoint animated:YES];
}
}
and I have an observer
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWasShown:)
name:UIKeyboardDidShowNotification object:nil];
and after I implemented my Next button (see below) the keyboardWasShown method is not called so it never checks if the active UITextField is hidden.
//functionality for next action
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
if (textField == self.emailAddress) {
[self.fullName becomeFirstResponder];
[self keyboardWasShown:(NSNotification*)UIKeyboardDidShowNotification];
}
else if (textField == self.fullName) {
[self.password becomeFirstResponder];
}
else if (textField == self.password) {
[self.confirmPassword becomeFirstResponder];
}
[textField resignFirstResponder];
return YES;
}
What would be the best approach to call keyboardWasShown when the user clicks the Next button? I tried making it a public method but I kept getting errors when I tried to call it manually.
One way to avoid this is to resign the responder prior to setting the next responder, which will ensure that the keyboardWasShown notification was called. For example, based on your code you could use the following:
...
else if (textField == self.fullName) {
[self.fullName resignFirstResponder];
[self.password becomeFirstResponder];
}
...
Whilst this might seem odd, it should be noted that the keyboard doesn't actually disappear/reappear.
You may want to look into this
If your fields arent in a table the same sort of logic can apply to other cases.
I am looking for a simple answer for this problem...
I have a UITextView in which the user can start typing and click on DONE and resign the keyboard.
When the wants to edit it again, I want the cursor (the blinking line) to be at the first position of the textView, not at the end of textView. (act like a placeholder)
I tried setSelectedRange with NSMakeRange(0,0) on textViewDidBeginEditing, but it does not work.
More Info:
It can be seen that.. when the user taps on the textView the cursor comes up at the position where the user taps on the textView.
I want it to always blink at starting position when textViewDidBeginEditing.
The property selectedRange can not be assigned at "any place", to make it work you have to implement the method - (void)textViewDidChangeSelection:(UITextView *)textView, in your case:
- (void)textViewDidChangeSelection:(UITextView *)textView
{
[textView setSelectedRange:NSMakeRange(0, 0)];
}
you will have to detect when the user is beginning editing or selecting text
My solution:
- (void) viewDidLoad {
UITextView *textView = [[UITextView alloc] initWithFrame: CGRectMake(0, 0, 200, 200)];
textView.text = #"This is a test";
[self.view addSubview: textView];
textView.delegate = self;
[textView release];
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget: self action: #selector(tapped:)];
[textView addGestureRecognizer: tap];
[tap release];
}
- (void) tapped: (UITapGestureRecognizer *) tap {
[textView becomeFirstResponder];
}
- (void) textViewDidBeginEditing:(UITextView *)textView {
textView.selectedRange = NSMakeRange(0, 0);
}
I guess it's UITextView internal mechanism to set the cursor when user taps on it. We need to override that by attaching a tap gesture recognizer and call becomeFirstResponder instead.
I was facing the same issue - basically there's a delay when becoming first responder that doesn't allow you to change selectedRange in any of textView*BeginEditing: methods. If you try to delay the setSelectedRange: (let's say with performSelector:withObject:afterDelay:) it shows ugly jerk.
The solution is actually pretty simple - checking order of delegate methods gives you the hint:
textViewShouldBeginEditing:
textViewDidBeginEditing:
textViewDidChangeSelection:
Setting selectedRange in the last method (3) does the trick, you just need to make sure you reposition the cursor only for the first time when the UITextView becomes first responder as the method (3) is called every time you update the content.
A BOOL variable set in shouldChangeTextInRange: one of the methods (1), (2) and check for the variable in (3) should do the trick ... just don't forget to reset the variable after the reposition to avoid constant cursor reset :).
Hope it helps!
EDIT
After few rounds of testing I decided to set the BOOL flag in shouldChangeTextInRange: instead of (2) or (3) as it proved to be more versatile. See my code:
#interface MyClass
{
/** A flag to determine whether caret should be positioned (YES - don't position caret; NO - move caret to beginning). */
BOOL _isContentGenerated;
}
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
// deleting
if([text length] == 0)
{
// deleting last character
if(range.length == [[textView text] length])
{
// reached beginning
/**
code to show placeholder and reset caret to the beginning
*/
_isContentGenerated = NO;
}
}
else
{
// adding
if(range.location == 0)
{
/**
code to hide placeholder
*/
_isContentGenerated = YES;
}
}
return YES;
}
- (void)textViewDidChangeSelection:(UITextView *)textView
{
if(!_isContentGenerated)
{
[textView setSelectedRange:NSMakeRange(0, 0)];
}
}
I haven't worked enough with that to help you fully, but what happens when you try to play with different selectedRanges? Say, if you do [... setSelectedRange:[NSMakeRange(0,1)]] or [... setSelectedRange:[NSMakeRange(1,0)]]? Does it move the cursor anywhere?
So I ended up adding a UILabel over the UITextView which acts as a placeholder for the textView. Tapping on the UILabel would send the action down to the textView and becomeFirstResponder. Once you start typing, make the label hidden.
[_detailAreaView setTextContainerInset:UIEdgeInsetsMake(8, 11, 8, 11)];
This is my first iAd for iPhone.
In development mode, if I switch my iPhone to airport mode, my app being debugged never ever gets this event.
But, if I start app with airport off, I get the 'bannerViewDidLoadAd' event okay. And if airport turned on -- never get didFailToReceiveAdWithError.
#interface ViewController : UIViewController <ADBannerViewDelegate> {
ADBannerView* adView;
}
#property(nonatomic, retain) IBOutlet ADBannerView *adView;
...
- (void)viewDidLoad
{
... (adView is from Interface Builder )
adView.requiredContentSizeIdentifiers = [NSSet setWithObject:ADBannerContentSizeIdentifier320x50];
adView.currentContentSizeIdentifier = ADBannerContentSizeIdentifier320x50;
[self.view addSubview:adView];
adView.delegate=self;
[super viewDidLoad];
}
- (void)bannerViewDidLoadAd:(ADBannerView *)banner
{
NSLog(#"bannerViewDidLoadAd");
if ( adView.hidden )
{
NSLog(#"going visible");
[UIView beginAnimations:#"animateAdBannerOn" context:NULL];
adView.hidden = NO;
// banner is invisible now and moved out of the screen on 50 px
//banner.frame = CGRectOffset(banner.frame, 0, 50);
[UIView commitAnimations];
}
}
- (void)bannerView:(ADBannerView *)banner didFailToReceiveAdWithError:(NSError *)error
{
NSLog(#"didFailToReceiveAdWithError");
if( !adView.hidden ) // ad banner displayed, but lost ad network
{
NSLog(#"going hidden");
[UIView beginAnimations:#"animateAdBannerOff" context:NULL];
adView.hidden = YES;
// banner is visible and we move it out of the screen, due to connection issue
//banner.frame = CGRectOffset(banner.frame, 0, -50);
[UIView commitAnimations];
}
}
The only time
- (void)bannerView:(ADBannerView *)banner didFailToReceiveAdWithError:(NSError *)error
is called is when an ad is already displayed and receives an error. When you are in airplane mode the initial ad is never displayed therefore this method is not called.
*Edit for clarity
If you check the Apple Developer Documentation you notice you have 2 options:
To assist you in validating your implementation, the iAd Network
occasionally returns errors to test your error handling code.
You can also test your error handling support manually by turning your device’s wireless capability off.
http://developer.apple.com/library/ios/#DOCUMENTATION/UserExperience/Conceptual/iAd_Guide/TestingiAdApplications/TestingiAdApplications.html#//apple_ref/doc/uid/TP40009881-CH6-SW1
Take into account that you can't turn off wireless for the iOS simulator. You need to disable the network connection of your development system:
IPhone Connectivity Testing: How do I force it to lose connection?
I have a problem with removing a subview, or more precisely with checking if it is still there after having deleted it.
My app first adds a subview to self.view.
[self.view addSubview:tabsClippedView];
Then it adds another subview to this subview (to which it adds several buttons as subviews, but I guess this is unimportant in this context):
[tabsClippedView addSubview:tabsView];
Finally, I have a method which allows the tabsView to be deleted and then created again. I need to do this so as to update the number of buttons in that tabsView (as the user can delete buttons). The method looks basically like this:
[self.tabsView removeFromSuperview];
After that I call a method called showTabs (which I already called in the very beginning of the app in order to add the subViews). This is where it all becomes problematic and where my app crashes (I get no error in the debug console, so I don't really know what the issue is...):
if ([tabsClippedView isDescendantOfView:self.view]) {
NSLog(#"There is already a tabsClippedView.");
} else {
NSLog(#"There is no tabsClippedView. I'll add one...");
[self initTabsClippedView];
}
This is where the app crashes: when trying to assess if tabsView isDescendantOfView (I don't get any of the following logs):
if ([tabsView isDescendantOfView:tabsClippedView]) {
NSLog(#"There is already a tabsView");
} else {
NSLog(#"There is no tabsView for the buttons. I'll add one including buttons.");
[self initTabs];
}
I'd be grateful for any suggestions where the problem could be.
EDIT:
These are the methods to set up my views:
-(void) initTabsClippedView { // sets up tabsClippedView
NSLog(#"initTabsClippedView method started...");
CGRect tabsClippedFrame = CGRectMake(258,30,70,81*6);
tabsClippedView = [[[UIView alloc] initWithFrame:tabsClippedFrame] autorelease];
tabsClippedView.clipsToBounds = true;
[self.view addSubview:tabsClippedView];
NSLog(#"initTabsClippedView method ended.");
}
-(void) initTabs {
NSLog(#"initTabs started. Adding buttons to tabsClippedView...");
CGRect tabsFrame = CGRectMake(-30,0,50,480);
tabsView = [[[UIView alloc] initWithFrame:tabsFrame] autorelease];
[tabsClippedView addSubview:tabsView];
// sets up buttons in tabsClippedView
And this is where I delete the tabsClippedView (triggered by a button found in tabsClippedView):
-(void)tabDelete:(id)sender
{
UIButton *button = (UIButton *)sender;
[UIView animateWithDuration:0.75
delay:0
options:UIViewAnimationOptionCurveEaseIn
animations:^{
button.transform = CGAffineTransformMakeTranslation(-30, 0);
}
completion:^(BOOL finished){
[UIView animateWithDuration:0
delay:0
options:UIViewAnimationOptionCurveEaseIn
animations:^{
[self.tabsView removeFromSuperview];
//...
}
completion:^(BOOL finished){
NSLog(#"tabsView removed from Superview. Objects Deleted.");
[self showTabs];
NSLog(#"TabDelete finished. Button removed and tabsView updated accordingly.");
}
];
}];
And this is the showTabs method which was already called when I started the app:
-(void)showTabs {
NSLog(#"showTabs started...");
currentView = #"Tabs";
if ([tabsClippedView isDescendantOfView:self.view]) {
NSLog(#"There is already a tabsClippedView.");
} else {
NSLog(#"There is no tabsClippedView. I'll add one...");
[self initTabsClippedView];
}
if ([tabsView isDescendantOfView:tabsClippedView]) {
NSLog(#"There is already a tabsView");
} else {
NSLog(#"There is no tabsView for the buttons. I'll add one including buttons.");
[self initTabs];
}
Is it possible that you are getting EXC_BAD_ACCESS? Is it possible that the app is crashing because tabsView is deallocated when you send isDescendantOfView: to it. If you run with breakpoints enabled it should tell you the reason for the crash. If it is an EXC_BAD_ACCESS problem you should try NSZombie.
To activate NSZombie do the following:
Get info of the executable.
Go to the arguments tab.
In the "Variables to be set in the environment:" section add:
Name: NSZombieEnabled
Value: YES
Then run your app as usual and when it crashes it should tell you which deallocated object received what message.
EDIT: Just saw your edit. I think I nailed it. You're autoreleasing the views when you create them, so when they are removed from their superviews they are no longer retained and thus deallocated. You're app crashes because you're trying to run methods on deallocated views.
EDIT 2: Thought I should tell you that there is a better solution than the one posted by Praveen S.
Change your code as follows:
[tabsClippedView release];
tabsClippedView = [[UIView alloc] initWithFrame:tabsClippedFrame];
and
[tabsView release];
tabsView = [[UIView alloc] initWithFrame:tabsFrame];
The above code does the same thing as the code posted by Praveen S, but without the autorelease. An autorelease is more expensive than a regular release and should only be used when needed and in this case it isn't.
Rather than releasing before you allocate a new view you probably want to release the view when you're done with it:
[tabsView removeFromSuperview];
[tabsView release];
tabsView = nil;
or simply
[tabsView removeFromSuperview];
self.tabsView = nil;
and then instead of:
if ([tabsView isDescendantOfView:tabsClippedView]) ...
you can use:
if (tabsView) ...
As you might have noticed, there really is no need for you to retain the view. You could just as well do the following:
tabsView = [[UIView alloc] initWithFrame:tabsFrame];
[tabsClippedView addSubview:tabsView]; // This retains tabsView
[tabsView release];
and then to remove the view you would use:
[tabsView removeFromSuperview]; // This will release the tabsView
tabsView = nil;
Also remember to set the views to nil in viewDidUnload.
EDIT 3: Why self made such a difference:
What you need to understand is how properties and reference counting works. There are books and such you could read about it. I'm sure Google can provide you with some good references as well.
The difference between
self.tabsView = [[UIView alloc] initWithFrame:frame];
and
tabsView = [[UIView alloc] initWithFrame:frame];
is that self.tabsView is accessing the properties setter, while tabsView is accessing the instance variable directly.
A nonatomic, retain property's implementation looks something like the following:
- (void)setTabsView:(UIView *)view
{
if (view != tabsView) {
[tabsView release];
tabsView = [view retain];
}
}
So the property is taking care of the memory management for you. In my solution I take care of the memory management myself and thus I don't need the property to do it for me, so I don't use it.
I hope this explains why self made such a difference.
Change your code as follows:
self.tabsClippedView = [[[UIView alloc] initWithFrame:tabsClippedFrame] autorelease];
and
self.tabsView = [[[UIView alloc] initWithFrame:tabsFrame] autorelease];