Why is uiview repositioned when application re-enters active state - ios

I have an iOS application where the main screen that is launched (A login screen) has 3 text fields and a button to "Login" below the fields. There is a content view within the main view which holds the fields and the button and it is vertically centered. When any of the 3 fields begins editing, the view shifts up such that they are visible and so is the button. The relevant code for this is:
(void)viewDidLoad {
[super viewDidLoad];
[userName addTarget: self action: #selector(slideFrameUp:) forControlEvents: UIControlEventEditingDidBegin];
[userName addTarget: self action: #selector(slideFrameDown:) forControlEvents: UIControlEventEditingDidEnd];
//.... same thing for 2 other fields
}
-(void) slideFrameUp: (id)sender {
CGFloat slidePoints = 116.0;
[self slideFrame: YES distance: slidePoints];
}
-(void) slideFrameDown: (id)sender {
CGFloat slidePoints = 116.0;
[self slideFrame: NO distance: slidePoints];
}
-(void) slideFrame:(BOOL) up distance: (NSInteger)amount {
const float movementDuration = 0.3f; // tweak as needed
int movement = (up ? -amount : amount);
[UIView beginAnimations: nil context: nil];
[UIView setAnimationBeginsFromCurrentState: YES];
[UIView setAnimationDuration: movementDuration];
self.view.frame = CGRectOffset(self.view.frame, 0, movement);
[UIView commitAnimations];
}
This works fine and does what we need. The one hiccup is that when the user is editing a text field and presses the home button and then opens the app again, when the app re-opens (if it only went to background and wasn't killed) it will be positioned exactly how it was previously initially (for like 1/10th of a second) and then it will reset back to center with the keyboard still present, thus leaving it lower on the screen than it should be and hiding some fields. The thing is none of my code runs when the view position resets. slideFrame is not called at all either when the app enters background or when it goes back to active. None of my view related methods (viewDidLoad/viewDidAppear/viewWillAppear/viewWillDisappear, etc) are called on entering background or active. I'm wondering why does ios reposition my view when going back to active, and how can I prevent it?
I figured out one solution which I didn't really like. What I did was observe UIApplicationDidEnterBackgroundNotification and then dismiss the keyboard using the code:
[self.view endEditing: YES];
This seems to work fine except when the app opens the user is no longer focused in the field. I would prefer though if the app just opened back up as it was. I'm also just kind of curious why the system behaves this way and if maybe there is a more elegant way to handle this.

My approach would be to dismiss the keyboard as the app goes to background and set focus on your text field in viewWillAppear:

Related

UITapGestureRecognizer on UIView gets fired after I tap on my MPVolumeSlider

I found some questions and answers here on stackoverflow for that problem, but none of the solutions there solved my problem.
My iOS App has the ability to play some music with a nice music player. I designed it with Xcode's Interface Builder and dragged out a UIView and changed its class to MPVolumeView. Everything works fine when I'm debugging my app on my iPhone 6.
Here is my problem: I also dragged out a UITapGestureRecognizer on my whole view which contains my controls like
play/pause, next/previous track (...)
and also my MPVolumeView. When I tap on that view it should fade out and disappear. Then I added a UITapGestureRecognizer on my UIImageView which shows my artwork image of the song. When I tap this image view, it should fade in my view with all controls in int - that's working properly.
BUT: When I slide the knob of the volume slider just a little bit, or if I am just touching it, the view still disappears. It seems like my MPVolumeView is forwarding my touch or something like that. I tried setting userInteractionEnabled = false on my volume slider, but that didn't help. I also set the delegate of my gesture recognizer to self and added the
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
NSLog(#"tapped");
if([gestureRecognizer.view isMemberOfClass:[UIImageView class]]) {
return true;
}
return false;
}
function to my code, which returns true or false, depending on which view I'm tapping. When I'm accessing the gestureRecognizer.view property, it doesn't recognize my MPVolumeView, just the UIView in the background.
Here my two methods which are fired after when the TapGestureRecognizers are fired:
- (IBAction)overlayViewTapped:(UITapGestureRecognizer *)sender {
if(sender.state == UIGestureRecognizerStateEnded) {
[UIView animateWithDuration:0.3
delay:0.0
options:UIViewAnimationOptionAllowUserInteraction
animations:^{ self.blackOverlayView.alpha = 0.0; self.normalTimeLabel.alpha = 1.0; }
completion:nil];
}
}
- (IBAction)imageViewTapped:(UITapGestureRecognizer *)sender {
[UIView animateWithDuration:0.3
delay:0.0
options:UIViewAnimationOptionAllowUserInteraction
animations:^{ self.blackOverlayView.alpha = 1.0; self.normalTimeLabel.alpha = 0.0; }
completion:nil];
}
Please help me, I'm nearly going nuts with that ..
EDIT: My music player looks like this:
After I tap anywhere on the view (except the subviews), the view should fade out and hide everything, just show the artwork image of the song and the current elapsed time. This will look like this:
As I said - the problem is, if I just tap the volume slider or slide it just a little bit, my UITapGestureRecognizer fires and fades out my complete view. How can I prevent that?
It is behaving the way it is simply because you added the gesture recognizer to the entire UIView, which includes the volume slider and whatnot.
Instead of detecting the touch in the entire view, check to see if the touch is in the area you want it.
Create a CGRect property, I'll call it touchArea:
#property CGRect touchArea;
Then specify the size of the touchArea (you can do this in the viewDidLoad):
touchArea = CGRectMake(0.0, 240.0, 320.0, 240.0);
You will have to find out where you want this and how big it should be and replace my example values with the real ones. A simple way of cheating this is to take something like a UILabel in IB and positioning and sizing it to your desire, then go to the size inspector pane and get the x, y, width and height values.
Then, before you do your fade animation, check to see if the touch was in the touchArea:
- (void)handleGesture:(UIGestureRecognizer *)gestureRecognizer
{
CGPoint touchPoint = [gestureRecognizer locationInView:self.view];
if (CGRectContainsPoint(touchArea, touchPoint))
{
//do your animation here.
}
}
As a note, I would set a BOOL to check whether or not the view is faded in or out, so you can always check before animating.

iOS7 Popup or Notification lasting a few seconds

Is there a standard way to display a short message to the user in an iOS application and then disappear automatically.
I have an app where a user can make changes. If he/she saves the changes I want to give feedback. But it's probably annoying if an AlertBox is displayed all the time and the user needs to klick on a button.
You have a few options that you can try. What you want might be the following :
Create an UIView, where you want to give feedback. This way, you can create it exactly the way you want it.
Set it hidden by default. yourView.alpha = 0.0f; => required if you want to animate when it appears, if not, just use yourView.hidden = true and ignore all animation stuff below, just replace it by yourView.hidden = false.
When you need your user to see your feedback you can animate it by the following :
[UIView beginAnimations:#"fadeIn" context:nil];
[UIView setAnimationDuration:0.5]; // add the value you want
yourView.alpha = 1.0f;
[UIView commitAnimations];
Right after, to hide it again but later, add the following :
NSTimeInterval timeInterval = 1.0f; // how long your view will last before hiding
[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self selector:#selector(hideView) userInfo:nil repeats:NO];
Finally, create your hideView method, that will be called by your timer :
-(void) hideView {
[UIView beginAnimations:#"fadeOut" context:nil];
[UIView setAnimationDuration:0.5]; // add the value you want
yourView.alpha = 0.0f;
[UIView commitAnimations];
}
Maybe there are better ways to do it, but i think this one will do the trick, and lets you create your view as you need it. The trick with animation will add a nice look and feel to your feedback. Hope that helps :)
You can create a Custom View by UIView and use the NSTimer to display it for a certain time and remove the view
You can display a custom view with [.... addSubview: yourView].
After that, u create a NSTimer, with a 1 second delay for example.
On the timerĀ“s selector, u can do a backcount, and show for example
on your custom view, how much remain time the view will be shown.
On the selector, make an if, and after X seconds just do
[youtView removeFromSuperview];
You can use a custom view with scheduled timer to present some info and then hide it when the timer completes.
However, if you are looking for a Android-style toast that shows pop-up like information and then hides after a specific time. Then, you might consider using Toast Control for iOS. This is one of the easiest controls (Atleast, I've come across) for implementing simple popup without having user to cancel it.

Resetting UIWebView Frame kills 75% of touchable area

I've got a webpage that is being viewed with a UIWebView. It's got an input form, so I'm shifting the UIWebView up when the keyboard is summoned, and then moving it back down when the keyboard is dismissed, so that the selected input field is centered. Code below:
-(void)keyboardWillShow:(NSNotification*) aNotification {
float offset = [self calculateYOffsetForKeyboard];
[UIView animateWithDuration:0.3f delay:0.05f options:UIViewAnimationOptionBeginFromCurrentState animations:^ {
self.web.frame = CGRectMake(webView_rect_x, offset, self.web.scrollView.frame.size.width, self.web.scrollView.frame.size.height);
} completion:nil];
}
-(void)keyboardWillHide: (NSNotification*) aNotification {
[UIView animateWithDuration:0.3f delay:0.05f options:UIViewAnimationOptionBeginFromCurrentState animations:^ {
self.web.frame = CGRectMake(webView_rect_x, 0, self.web.scrollView.frame.size.width, self.web.scrollView.frame.size.height);
} completion:nil];
}
Here's the weird part: when I do that (tap an input field to summon the keyboard then tap "Done" to dismiss it), everything but the top-left quarter of the UIWebView becomes untouchable. It looks fine, animates up and down perfectly, displays the content properly, but anything right of or below the center point doesn't respond to touches (i.e. several buttons can't be pressed, etc).
Any idea why this might happen? I've commented out lines and determined that it is the "self.web.frame" that is causing the problem: remove that movement and it works fine. I've also NSLog'd the "self.web.scrollView.frame.size.width" and "self.web.scrollView.frame.size.height" values and they seem to be changing as expected and returning to normal. Also the calculateYOffsetForKeyboard method is returning a proper value, (it changes based on which field is selected, but it's generally around -108.5).
So why would moving the frame back and forth alter the view's ability to respond to touches? Any ideas?

UIButton not responding after animation

I would prefer first download the project from below link and then continue with question (only 36kb)
Download Link
At start what I have is like below.
When I click My Office button, I am calling action actionSeenButton which will print NSLog(#"actionSeenButton");
- (IBAction)actionSeenButton:(id)sender {
NSLog(#"actionSeenButton");
}
This works perfect.
When I click, Show hidden button, I am sliding view by 100 and showing the image and buttons that I have at the top, as shown in below image
Code used is
- (IBAction)showHiddenButton:(id)sender {
CGAffineTransform translation = CGAffineTransformIdentity;
translation = CGAffineTransformMakeTranslation(0, 100);
[UIView beginAnimations:nil context:nil];
self.view.transform = translation;
[UIView commitAnimations];
}
When I click this button, I am calling action actionHiddenButton which will print NSLog(#"actionHiddenButton");
- (IBAction)actionHiddenButton:(id)sender {
NSLog(#"actionHiddenButton");
}
BUT the problem is, when I click the new button that I see, action is not getting called.
Any idea why this is happening?
Note
When I move the top hidden button from y=-70 to y=170, action is getting called.
Sample project can be downloaded from here
What I wanted to implement is, showing three buttons (as menu) on the top in one line by moving view down.
verify that your button is not behind the frame of another view. even if the button is visable, if there is something covering it up it wont work. i don't have access to xcode at the moment but my guess is your view "stack" is prohibiting you from interacting with the button. a button is esentually a uiview and you can do all the same animations to buttons and labels that you can with views. your best bet is to leave the view in the background alone and just move your buttons. since your "hidden" button isn't part of your main "view" hiarchy thats where your problem is.
upon further investigation, your problem is related to auto-layout and making sure your button object stays in the view hierarchy. if you turn off auto-layout you will see where the problem is. when you animate the main view down the "hidden" button is off of the view and there for inactive. the easiest solution is to just animate the buttons. the next best solution closest to what you have is to add another view onto your "main view" and then put the buttons into that view. also why do you have that background image twice? why not just set the background color of your view to that same yellow?
I downloaded your project and it seems the translation you're making for self.view. So the actionHiddenButton is not in the frame.Its better to have the controls you want to animate in the separate view.
If you want to see the problem, after your view get transformed set clipsToBounds to YES. Like
self.view.transform = translation;
self.view.clipsToBounds = YES;
Yipeee!!! Below is how I did.
.h
Added new variable.
#property (retain, nonatomic) NSString *hideStatus;
.m
-(void) viewDidAppear:(BOOL)animated {
NSLog(#"viewDidAppear");
CGAffineTransform translation = CGAffineTransformIdentity;
translation = CGAffineTransformMakeTranslation(0, -100);
self.view.transform = translation;
self.view.clipsToBounds = YES;
[UIView commitAnimations];
self.view.frame = CGRectMake(0,-80,320,560);
hideStatus = #"hidden";
}
- (IBAction)showHiddenButton:(id)sender {
NSLog(#"hideStatus===%#", hideStatus);
CGAffineTransform translation = CGAffineTransformIdentity;
if ([hideStatus isEqualToString:#"hidden"]) {
translation = CGAffineTransformMakeTranslation(0, 0);
hideStatus = #"shown";
} else {
translation = CGAffineTransformMakeTranslation(0, -100);
hideStatus = #"hidden";
}
[UIView beginAnimations:nil context:nil];
self.view.transform = translation;
self.view.clipsToBounds = YES;
[UIView commitAnimations];
}
Attached is the sample project. You can download from here.

How would I create a status message that briefly appears "above" a UITableView (like UIRefreshControl)?

I'm trying supplement a UIRefreshControl with a UIView containing a message (statusMessageView) that appears "above" a UITableViewController after the app launches. This view will inform the user that the UITableViewController is already refreshed and does not need refreshing.
Here is a breakdown of what I am trying to accomplish:
The app is launched, the UITableViewController appears normal then scrolls down 44px to reveal statusMessageView (with a height of 44px).
statusMessageView stays visible for 2 seconds
The UITableViewController animates a scroll up to it's original position, effectively "tucking" statusMessageView away. (like UIRefreshControl, but animated with code)
Note: This statusMessageView will be used in conjunction with a UIRefreshControl, so it needs to "go away" after it is displayed so that the UIRefreshControl can be used normally
I have looked at other 3rd party "pull to refresh" controllers, but I think that is overkill due to the fact I am using UIRefreshControl
It seems like the other answers here are providing solutions for a notification that drops down below the UINavigationBar. If you're still looking for a solution that sits in the scrollview of the UITableView, then I would add a custom table header (not section header) to the table view.
Here are the rough steps necessary to accomplish this:
1. Create the initial header view on load
I typically use a UIViewController subclass that owns a UITableView instance variable (instead of using a UITableViewController), but you should be able to accomplish this with either set up. In your tableview set up code (probably in viewDidLoad), where you set things like backgroundColor, contentInset, separatorStyle, etc, create a UILabel that will become your header. Then set this UILabel to be the tableHeaderView of your tableView. Of course, if you're looking to make something a bit more complicated for this "notification section", feel free to make it a UIView with a nested UILabel + something else. So something like:
UILabel *headerLabel = [[UILabel alloc] initWithFrame:CGRectMake(0.0f, 0.0f, self.tableView.bounds.size.width, 44.0f)];
headerLabel.backgroundColor = [UIColor clearColor]; // Just in case you have some fancy background/color in your table view
headerLabel.textAlignment = UITextAlignmentCenter;
headerLabel.font = ....
headerLabel.textColor = ....
headerLabel.text = #"You are an awesome user!";
self.tableView.tableHeaderView = headerLabel;
2. Set your tableview to load "normally" (ie. not show header)
Again, inside viewDidLoad, you need to set your tableview's contentOffset and alwaysBounceVertical properly to hide this header view on load. contentOffset set to the height of the header will start the tableview's y coordinate right below the header. alwaysBounceVertical set to YES will allow your tableview to behave correctly even if your tableview's contentsize is less than your screen size. So something like:
self.tableView.contentOffset = (CGPoint){0.0f, 44.0f};
self.tableView.alwaysBounceVertical = YES;
3. Add the slide down and slide away
Ok, now there are a couple of ways you can do this. On viewDidAppear, you can create a chained UIView animation where the first animation slides the tableview down (ie. sets contentOffset to {0.0f, 0.0f}) is delayed by one second and the second animation slides the tableview back up (ie. sets contentOffset to {0.0f, 44.0f}) is delayed by two seconds. Or you can use GCD and schedule the two animations as async + delayed blocks. Either way is fine (and there are probably two or three other good ways to accomplish this), but just to get the idea across... you can chain the animation like this:
__weak MyCustomViewController *me = self;
[UIView
animateWithDuration:0.4f
delay:1.0f
options:UIViewAnimationOptionAllowUserInteraction
animations:^
{
me.tableView.contentOffset = (CGPoint){0.0f, 0.0f};
}
completion:^(BOOL finished)
{
if (me.tableView)
{
[UIView
animateWithDuration:0.4f
delay:2.0f
options:UIViewAnimationOptionAllowUserInteraction
animations:^
{
me.tableView.contentOffset = (CGPoint){0.0f, 44.0f};
}
completion:^(BOOL finished)
{
if (me.tableView)
{
me.tableView.tableHeaderView = nil; // If you want to completely get rid of this notification header
me.tableView.contentOffset = (CGPoint){0.0f, 0.0f}; // I'm unsure if this will cause the tableview to jump... if it does, you can animate the headerview away instead of animating the tableview up to hide the header, then setting header to nil and reseting the contentOffset
// or
me.tableView.tableHeaderView.hidden = YES; // If you want to just hide the header
}
}];
}
}];
I wrote the sample code without testing any of it... hehe, so it probably won't work as it. But happy to help you flesh this out if you need more help! Good luck.
Update: Allowing user scrolling to cancel animation
I'm not too sure what you want the user interaction on the table to do to the animation. If you just want the animation to be canceled when the user starts scrolling, then I would use GCD (see code below). But I can see other ways you can have the animation work with user touch, so it depends on what you're looking for. In any case, let's say any user touch should disable the next scheduled animation then it could be achieved using two functions like:
- (void)scheduleShowHeaderAnimation
{
__weak MyCustomViewController *me = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1.0f * NSEC_PER_SEC), dispatch_get_main_queue(), ^ // Start animating after 1 sec delay
{
if (me.tableView) // Make sure the tableview is still around
{
if (! me.tableView.tracking && // Don't show animation if user has begun to touch contentview
! me.tableView.dragging && // Don't show animation if user has begun to drag contentview
! me.tableView.decelerating) // Don't show animation if dragging happened and scrollview is starting to decelerate
{
[UIView
animateWithDuration:0.4f
delay:0.0f
options:UIViewAnimationOptionAllowAnimatedContent // This will make sure user can interact with tableview while animation is going on
animations:^
{
me.tableView.contentOffset = (CGPoint){0.0f, 0.0f};
}
completion:^(BOOL finished)
{
[me scheduleHideHeaderAnimation];
}];
}
}
});
}
- (void)scheduleHideHeaderAnimation
{
__weak MyCustomViewController *me = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2.0f * NSEC_PER_SEC), dispatch_get_main_queue(), ^ // Start animating after 2 secs delay
{
if (me.tableView) // Make sure the tableview is still around
{
if (! me.tableView.tracking && // Don't show animation if user has begun to touch contentview
! me.tableView.dragging && // Don't show animation if user has begun to drag contentview
! me.tableView.decelerating) // Don't show animation if dragging happened and scrollview is starting to decelerate
{
[UIView
animateWithDuration:0.4f
delay:0.0f
options:UIViewAnimationOptionAllowAnimatedContent // This will make sure user can interact with tableview while animation is going on
animations:^
{
me.tableView.contentOffset = (CGPoint){0.0f, 44.0f};
}
completion:^(BOOL finished)
{
if (me.tableView)
{
me.tableView.tableHeaderView = nil; // If you want to completely get rid of this notification header
me.tableView.contentOffset = (CGPoint){0.0f, 0.0f}; // I'm unsure if this will cause the tableview to jump... if it does, you can animate the headerview away instead of animating the tableview up to hide the header, then setting header to nil and reseting the contentOffset
// or
me.tableView.tableHeaderView.hidden = YES; // If you want to just hide the header
}
}];
}
}
});
}
I would call scheduleShowHeaderAnimation in the viewDidAppear.
Then to support hiding the header when the user has already scrolled the tableview down, I would implement either - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate or - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView of UIScrollViewDelegate and add something like:
if (self.tableView.tableHeaderView != nil)
{
self.tableView.tableHeaderView = nil;
}
// or
if (self.tableView.tableHeaderView.hidden == NO)
{
self.tableView.tableHeaderView.hidden = YES;
}
If there's more complicated interactions you want to support, or if you want the animation to respond to the user touch in different ways, you might need to override other UIScrollViewDelegate methods and when the user begins to interact with the scrollview (which is the parent class of the table view), then change the behavior of the animation.
Does this get you closer to what you're looking for?
besides the answer of #Khawar , there is another choice:
https://github.com/toursprung/TSMessages
You can use following library for this purpose. You can set the autohide time duration as per your needs. You can also customise its appearance.
https://github.com/tciuro/NoticeView
It can be done in a very simple way: you just create and insert a subview into your refresh control:
- (void)viewDidLoad {
[super viewDidLoad];
...
UIView *smView = [self statusMessageView];
[self.refreshControl insertSubview: smView atIndex: 0];
// to show and hide the view inserted in refresh control
// With this transparent loader color even the middle area is not covered
// so you can have all the space to show your message
self.refreshControl.tintColor = [UIColor colorWithWhite:1.0 alpha:0.0];
[self.refreshControl beginRefreshing];
[self afterDelay: 2.0 performBlock: ^(){
[self.refreshControl endRefreshing];
// this delay is needed to prevent loader animation becoming visible
// while the refresh control is being hidden
[self afterDelay: 0.25 performBlock: ^(){
self.refreshControl.tintColor = nil;
});
});
...
}
-(void)afterDelay: (float)seconds performBlock: (void (^)())block {
dispatch_time_t popTime =
dispatch_time(DISPATCH_TIME_NOW, (int64_t)(seconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), block);
}
-(UIView)statusMessageView {
...
return view;
}
View size you need is 320x43px (if it is higher the bottom of it will be visible with refresh control hidden), the middle area (approximately 35px) will be covered by the loader animation (but not when you initially show the message).
I use UIImageView to show application logo there (it's even simpler: see here).
I would probably prefer (as a user) not to see this message before I start pulling. When I start pulling I will see that no update is needed before refresh starts (it starts when you pull down approximately twice more than the height of this subview, so user will have time to see what's there and if refresh is needed). If there are unread items in the table (tweets, posts, whatever), you can show the number of unread items in that view.
But it's a matter of personal preference, the way it is done it seems to achieve exactly what you want in a very simple way. I tested, it all works.

Resources