Finish up existing iOS animation - ios

I have an application with multiple views that transitions to the main screen depending on which button was pressed. My current problem is that if the view is in the middle of animating then when the user selects another button then the whole layout becomes messed up. (ex: the views don't align with the screen meaning that they become a few pixels off)
What I would like to know is if there is a way to check if the view is currently animating and if so just have it animate to the last frame and skip anything in between. Below is a small piece of code that I have just tested based on what I have read on other user asked questions on SO:
-(IBAction)buttonPress:(id)sender
{
if([selectedView.layer.animationKeys count] > 0)
{
[selectedView.layer removeAllAnimations];
}
// Perform other calculations once the animation has stopped
}

There are many ways to do this but....
If you are using block animations you could set a "isAnimating" flag when the animation starts and set it again in the completion block. You could check the bool from anywhere and handle cases as needed.
As for needing code to execute after an animation occurs, but
// animation code in some method...
[UIView animateWithDuration:1.0
delay: 0.0
options: UIViewAnimationOptionCurveEaseIn
animations:^{
isAnimating = YES;
fooView.alpha = 0.0;
}
completion:^(BOOL finished){
isAnimating = NO;
[[NSNotificationCenter defaultCenter] postNotificationName:#"FooBeDone" object:nil userInfo:nil]
}];
-(IBAction)buttonPress:(id)sender {
if (isAnimating) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(doBar:)
name:#"FooBeDone"
object:nil];
// possibly disable button to prevent multiple taps?
} else {
[self doBar];
}
}
- (void)doBar {
// do what needs to be done when when the animation is over
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"FooBeDone" object:nil];
// possibly enable button again
}
edit: I added more code to show a possible notification method. Creating extended loops in your IBAction will lock the user interface until the loop finishes and you can get back to the main run loop, so it's highly advised to avoid it. Notifications should give you the same effect but allow your main run loop to continue unhindered.

Related

iAd freezes game's scene

I am developing a game using SpriteKit. I have iAds displayed in a view on the scene. When an ad in the Ad view is touched, the Ad appears, however, if I cross(X)/close the ad the scene in the game is frozen. I do not pause the scene or do anything on the events when the Ad appears and disappears. If I touch the ad again (now this is second time with frozen scene) and return to the scene, the scene un-freezes and everything starts to work as they were suppose to (strangely). I am not sure where is the problem in iAd or my app?
// Method is called when the iAd is loaded.
-(void)bannerViewDidLoadAd:(ADBannerView *)banner {
NSLog(#"Banner did load");
[self animateAdBanner];
}
-(void) animateAdBanner{
if(iAdsEnable){
[UIView animateWithDuration:6 delay:8
options:UIViewAnimationOptionAllowUserInteraction
animations:^{
[self.adBanner setAlpha:0.85];
}
completion:^(BOOL finished){
if(finished){
//[self.adBanner setAlpha:0.7];
}
}];
}
}
According to Apple's ADBannerViewDelegate Protocol Reference:
bannerViewActionShouldBegin:willLeaveApplication:
If the willLeave parameter is YES, then your application is moved to the background shortly after this method returns. In this situation, your method implementation does not need to perform additional work. If willLeave is set to NO, then the triggered action will cover your application’s user interface to show the advertising action. Although your application continues to run normally, your implementation of this method should disable activities that require user interaction while the action is executing. For example, a game might pause its game play until the user finishes watching the advertisement.
Basically meaning that as your game is pushed into the background, the game will pause.
The delegate has a further method to notify you when the banner view has finished its action:
bannerViewActionDidFinish:
Discussion
If your delegate paused activities before allowing an action to run, it should resume those activities when this method is called.
It seems you can either use the above delegate methods to implement your own pause/un-pause or you could use the a NSNotification in your Scene to pause/un-pause when your app moves into the background and foreground respectively.
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(pause)
name:UIApplicationWillResignActiveNotification
object:nil];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(unPause)
name:UIApplicationWillEnterForegroundNotification
object:nil];
Sources:
iAd Framework Reference and ADBannerViewDelegate Protocol Reference

Why is UIKeyboardWillShowNotification called every time another TextField is selected?

I have a project that contains a UIScrollView and many UITextField inside it.
For the first time I select a UITextField, UIKeyboardWillShowNotification is called, which is fine. But whenever I select new UITextField (THE KEYBOARD IS STILL THERE), UIKeyboardWillShowNotification is called again !!!, which is weird.
I also set a symbolic breakpoint for [UIResponder resignFirstResponder] and I see that it is hit before and after UIKeyboardWillShowNotification is called !!!
The other thing is that UIKeyboardWillHideNotification is only called when I hit the "Done" button on the keyboard
I'm sure to not call any resignFirstResponder, becomeFirstResponder, endEditing anywhere. (I mean not call wrongly)
What can cause this problem ?
Here is the stacktrace
To workaround the problem, I used the following code to cancel the UIKeyboardWillShowNotification callback if the keyboard's frame is not changing.
func keyboardWillShow(notification: NSNotification) {
let beginFrame = notification.userInfo![UIKeyboardFrameBeginUserInfoKey]!.CGRectValue()
let endFrame = notification.userInfo![UIKeyboardFrameEndUserInfoKey]!.CGRectValue()
// Return early if the keyboard's frame isn't changing.
guard CGRectEqualToRect(beginFrame, endFrame) == false else {
return
}
...
}
For Swift 3/4:
func keyboardWillShow(notification: Notification) {
let userInfo = notification.userInfo!
let beginFrameValue = (userInfo[UIKeyboardFrameBeginUserInfoKey] as? NSValue)!
let beginFrame = beginFrameValue.cgRectValue
let endFrameValue = (userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue)!
let endFrame = endFrameValue.cgRectValue
if beginFrame.equalTo(endFrame) {
return
}
// Do something with 'will show' event
...
}
The problem is I set inputAccessoryView for the UITextField, and this cause UIKeyboardWillShowNotification being called again when new UITextField is selected
This article Working With Keyboard on iOS explains this well
Additional changes take place when we connect an external keyboard to
the iPad. In this particular case, the notification behavior depends
on the inputAccessoryView property of the control which was the reason
for displaying the keyboard.
If inputAccessoryView is not present or its height is equal to 0
points, no keyboard notifications are sent. My guess is that this is
because in this case, no visual changes take place in application.
Otherwise, all notifications behave as expected – which means they are
being sent as in the majority of cases when the keyboard is displayed
or hidden in a normal (not undocked or split) state.
Whenever new UITextField is selected, the OS needs to compute the frame for the keyboard again, and the following notifications are posted
UIKeyboardWillChangeFrameNotification
UIKeyboardWillShowNotification
UIKeyboardDidChangeFrameNotification
UIKeyboardDidShowNotification
The same applies for when the TextField loses its first responder status
Note that using the same View for inputAccessoryView will cause UIKeyboardWillShowNotification only called once
In general I find that many things can cause spurious UIKeyboardWillShow and UIKeyboardWillHide notifications. My solution is to use a property to track whether the keyboard is already showing:
func keyboardShow(_ n:Notification) {
if self.keyboardShowing {
return
}
self.keyboardShowing = true
// ... other stuff
}
func keyboardHide(_ n:Notification) {
if !self.keyboardShowing {
return
}
self.keyboardShowing = false
// ... other stuff
}
Those guards block exactly the spurious notifications, and all is well after that. And the keyboardShowing property can be useful for other reasons, so that it is something worth tracking anyway.
The best approach is to Add notification & remove it once your purpose is solve.
like this .
- (void)viewWillAppear:(BOOL)animated
{
// register for keyboard notifications
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillShow)
name:UIKeyboardWillShowNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillHide)
name:UIKeyboardWillHideNotification
object:nil];
}
Now write your code for movement of views & textField in keyboardWillShow & revert them back to position in keyboardWillHide methods.
Also remove the observers
- (void)viewWillDisappear:(BOOL)animated
{
// unregister for keyboard notifications while not visible.
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIKeyboardWillShowNotification
object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIKeyboardWillHideNotification
object:nil];
}
You can also resign the responder when you press return key.
-(BOOL)textFieldShouldReturn:(UITextField *)textField {
[_txtFieldEmail resignFirstResponder];
[_txtFieldPassword resignFirstResponder];
return YES;
}
That should solve your issue.
For those not using inputAccessoryView but are still having problems, it may be due to using sensitive (password) fields. See this Stack Overflow post and answer: keyboardWillShow in IOS8 with UIKeyboardWillShowNotification
I have struggled with this, after half a day of searching around and experimenting I think this is the shortest and most reliable code. It is a mix of a number of answers most of which I forget where I found (parts of which are mentioned here).
My problem was a WKWebView which (when the user changed fields) would spawn a load of WillShow, WillHide, etc notifications. Plus I had problem with the external keyboard which still has the onscreen touch bar thing.
This solution uses the same animation code to "Open" and "Close" the keyboard, it will also cope with external keyboard being attached and custom keyboard views.
First register for the UIKeyboardWillChangeFrameNotification.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillChangeFrame:) name:UIKeyboardWillChangeFrameNotification object:nil];
Then you simply need to map the changes to your view in however you do it (change a hight or bottom constraint constant).
- (void)keyboardWillChangeFrame:(NSNotification *)notification
{
NSDictionary *userInfo = notification.userInfo;
CGRect keyboardEnd = [userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
CGRect convertedEnd = [self.view convertRect:keyboardEnd fromView:nil];
// Convert the Keyboard Animation to an Option, note the << 16 in the option
UIViewAnimationCurve keyAnimation = [userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue];
// Change the Height or Y Contraint to the new value.
self.keyboardHeightConstraint.constant = self.view.bounds.size.height - convertedEnd.origin.y;
[UIView animateWithDuration:[userInfo[UIKeyboardAnimationDurationUserInfoKey] floatValue]
delay:0.0
options:keyAnimation << 16
animations:^{
[self.view layoutIfNeeded];
} completion:nil];
}
The Animation to Option conversion seems to work (I can only find examples of it being used and not how/why), however, I am not convinced it will stay that way so it may be wise to use a "stock" option. It seems that the Keyboard uses some none specified Animation.

View that moves along with keyboard

I have an iOS app with a UIScrollView that basically looks like the Messages.app: content on the screen and on the bottom a text view and a button to add more content. When the keyboard appears the text view and button move up correctly.
I've set keyboardDismissMode so that dragging the keyboard down makes it disappear but during the process of the dragging, as the keyboard is moving down, how can I update my views' locations on screen to stay attached to it? It seems that the keyboard will change frame notification isn't fired during this process.
What's the "right" way of doing this?
Edit: I have a hunch it might be doable using an input view/accessory view, but not sure that's the right direction to go.
Pretty sure this behavior requires SPI. BUT: You could walk the view/window hierarchy, find the keyboard window, and apply a transform to it to move it during your interaction. (window.transform=)
Not sure what to do when your interaction ends however--maybe manually animate the keyboard away (using the above technique) to finish the keyboard hide, then when it's hidden, resign first responder without animating.
EDIT:
Sorry, I thought you wanted to move the keyboard, but if you want to observe the keyboards position, you can use KVO for that (as #KudoCC suggested)
static void * __keyboardCenterKVOContext = & __keyboardCenterKVOContext ;
-(void)setUpKeyboardObserver
{
UIView * keyboardView = ...?
[ keyboardView addObserver:self forKeyPath:#"center" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial context:__keyboardCenterKVOContext ] ;
}
Then implement the KVO observer:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ( context == __keyboardCenterKVOContext )
{
CGPoint newKeyboardCenter = [ [ change valueForKey:NSKeyValueChangeNewKey ] pointValue ] ;
// .. handle new keyboard position here ...
}
else
{
[ super observeValueForKeyPath:keyPath ofObject:object change:change context:context ] ;
}
}
Your KVO observer will be run every time the keyboard view changes it's center property. You might also try observing the keyboard window's frame and/or transform properties.
I have some code to help with KVO observing here, which might help you: https://gist.github.com/nielsbot/6873377
(The reason you might want to use my helper code is because it automatically removes KVO observation from objects that are being deallocated. This requires some run time fiddling, but in your case you don't really know when the keyboard window will be deallocated. Although maybe you can tear down KVO in your keyboardWillHide handler)
You can try notification to move your content when keyboard disappear and appear:
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector:#selector(keyboardDisappeared) name:UIKeyboardWillHideNotification object:nil];
[center addObserver:self selector:#selector(keyboardAppeared) name:UIKeyboardWillShowNotification object:nil];
-(void) keyboardDisappeared
{
[UIView animateWithDuration:1.0 delay:0 options:yourAnimationOption animations:^
{
self.view.frame = CGRectMake(self.view.frame.origin.x, self.view.frame.origin.y+100(same as you do in keyboardAppeared), self.view.frame.size.width, self.view.frame.size.height);
} completion::^(BOOL finished)
{
}];
}
-(void) keyboardAppeared
{
[UIView animateWithDuration:1.0 delay:0 options:yourAnimationOption animations:^
{
self.view.frame = CGRectMake(self.view.frame.origin.x, self.view.frame.origin.y-(as much you want), self.view.frame.size.width, self.view.frame.size.height);
} completion::^(BOOL finished)
{
}];
}

UIButton does not respond to removeFromSuperView when returning to VC

I have a UIButton that I implement programmatically as it is only required when I rotate the device.
The button needs to disappear when I go rotate and appear when I go landscape.
As long as I stay in the same ViewController I have no issues. I can rotate the device in anyway and the button appears and disappears as expected.
The app is a TabController based app and when I go to another tab the same behavior happens.
THIS IS THE PROBLEM
When I go back to the original view, the button appears, but then never disappears. It is almost like the removeFromSuperView is not being called, but even if it is, the button is not removed.
Any ideas why this is?
-(void)autoRotationDetection
{
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter]
addObserver:self selector:#selector(orientationChanged:)
name:UIDeviceOrientationDidChangeNotification
object:[UIDevice currentDevice]];
}
- (void) orientationChanged:(NSNotification *)note
{
UIDevice * device = note.object;
switch(device.orientation)
{
case UIDeviceOrientationPortrait:
/* start special animation */
[_menuButton removeFromSuperview];
break;
case UIDeviceOrientationPortraitUpsideDown:
/* start special animation */
break;
default:
break;
};
}
Then I call
-(void)viewWillAppear:(BOOL)animated
{
[self autoRotationDetection];
}
Sorry should have added that.
I've seen this kind of behavior (behaving differently after going to another tab and coming back again) when a call to [super viewWillAppear] is left out. Try adding that, and see if it fixes it.
After Edit:
I think an easier way to do this is to just look at the bounds of the view in viewWillLayoutSubviews, which is called every time there's a rotation (and other times as well, but for a simple thing like this, that shouldn't matter). In this example I'm hiding or showing rather than removing, but the concept should work for both.
-(void)viewWillLayoutSubviews {
BOOL portrait = self.view.bounds.size.height > self.view.bounds.size.width;
if (portrait) {
self.button.hidden = YES;
}else{
self.button.hidden = NO;
}
}
You could make this more efficient by keeping track of the value of portrait the last time this method was called, and only change the button's status if the portrait has changed.

Dealing with two screens and one activity indicator in iOS

I have 3 screens on my app.First is login. Second is search and third is process the task.
On login i retrieve data from a web service. It returns data in XML format. So the data is considerably large. So i am doing that task on a background thread like this to stop Mainthread freezing up on me:
-(BOOL)loginEmp
{
.....some computation
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
(unsigned long)NULL), ^(void) {
[self getAllCustomerValues];
});
}
-(void)getAllCustomerValues
{
....more computation.Bring the data,parse it and save it to CoreData DB.
//notification - EDIT
NSNotification *notification =[NSNotification notificationWithName:#"reloadRequest"
object:self];
[[NSNotificationCenter defaultCenter] postNotification : notification];
}
//EDIT
//SearchScreenVC.m
- (void)viewDidLoad
{
....some computation
[self.customerActIndicator startAnimating];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(stopActivityIndicator)
name:#"reloadRequest"
object:nil];
}
- (void)stopActivityIndicator
{
[self.customerActIndicator stopAnimating];
self.customerActIndicator.hidesWhenStopped = YES;
self.customerActIndicator.hidden =YES;
NSLog(#"HIt this at 127");
}
So on condition that login was successful, i move to screen 2. But the background thread is still in process( i know because i have logs logging values) . I want an activity indicator showing up here (2nd screen)telling user to wait before he starts searching. So how do i do it?How can i make my activity indicator listen/wait for background thread. Please let me know if you need more info.Thanks
EDIT: so I edited accordingly but the notification never gets called. I put a notification at the end of getAllCustomerValues and in viewDidLoad of SearchScreen i used it. That notification on 2nd screen to stop animating never gets called. What is the mistake i am doing.?Thanks
EDIT 2: So it finally hits the method. I dont know what made it to hit that method. I put a break point. I wrote to stop animating but it wouldn't. I wrote hidesWhenStoppped and hidden both to YES. But it still keeps animating.How do i get it to stop?
Ok, if it is not the main thread, put the following in and that should fix it.
- (void)stopActivityIndicator
{
if(![NSThread isMainThread]){
[self performSelectorOnMainThread:#selector(stopActivityIndicator) withObject:nil waitUntilDone:NO];
return;
}
[self.customerActIndicator stopAnimating];
self.customerActIndicator.hidesWhenStopped = YES;
self.customerActIndicator.hidden =YES;
NSLog(#"HIt this at 127");
}
Could you put your background operation into a separate class and then set a delegate on it so you can alert the delegate once the operation has completed?
I havent tried this, its just an idea :)
You could use a delegate pointing to your view controller & a method in your view controller like:
- (void) updateProgress:(NSNumber*)percentageComplete {
}
And then in the background thread:
float percentComplete = 0.5; // for example
NSNumber *percentComplete = [NSNumber numberWithFloat:percentComplete];
[delegate performSelectorOnMainThread:#selector(updateProgress:) withObject:percentageComplete waitUntilDone:NO];

Resources