I am trying to get a busy view displayed during a search process however it never seems to display.
What I am trying to achieve
User clicks on search button once they have entered the text in a UISearchBar.
The text entered is delegated to searchBarSearchButtonClicked.
Show a "Busy view" which is a UIView containing a UIActivityIndicatorView to indicate the search is underway.
Perform a search which communicates with a website.
On search completion remove the "Busy View".
Whats wrong
The search process is working fine, using the debugger I can see it moving through the searchBarSearchButtonClicked function fine however the "Busy View" never appears. The search takes 2-3 seconds so in theory I should see my "Busy View" appear for those 2-3 seconds.
Code Attempt 1 - Add and remove the "Busy View" from the superview**
- (void)searchBarSearchButtonClicked:(UISearchBar *)activeSearchBar {
[activeSearchBar setShowsCancelButton:NO animated:YES];
[activeSearchBar resignFirstResponder];
[busyView activateSpinner]; //start the spinner spinning
[self.view addSubview:busyView]; //show the BusyView
Search *search = [[Search alloc]init];
results = [search doSearch:activeSearchBar.text];
[busyView deactivateSpinner]; //Stop the spinner spinning
[busyView removeFromSuperview]; //remove the BusyView
[search release]; search = nil;
}
Results Of Attempt 1 - It does not appear, however if I comment out the removeFromSuperview call the Busy View remains on screen.
Code Attempt 2 - using animations
- (void)searchBarSearchButtonClicked:(UISearchBar *)activeSearchBar {
[activeSearchBar setShowsCancelButton:NO animated:YES];
[activeSearchBar resignFirstResponder];
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:#selector(animationDidStop)];
[busyView activateSpinner]; //start spinning the spinner
[self.view addSubview:busyView]; //show the busy view
[UIView commitAnimations];
Search *search = [[Search alloc]init];
results = [search doSearch:activeSearchBar.text];
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:#selector(animationDidStop)];
[busyView deactivateSpinner]; //sets stops the spinner spinning
[busyView removeFromSuperview]; //remove the view
[UIView commitAnimations];
[search release]; search = nil;
}
Results of attempt 2 - Did not see the "Busy View" appear
Well, Here's my solution:
Create singleton class MyLoadingView. This class should contain show and hide static methods.
In MyLoadingView constructor you should manually create a view with semi-transparent black background and activityview inside of it. Place this view into [[UIApplication sharedApplication] keyWindow]. Hide it.
[MyLoadingView show] invocation just brings myLoadingView object to front in it's container: [[[UIApplication sharedApplication] keyWindow] bringSubviewToFront:myLoadingViewSharedInstance];. Also don't forget to start activityview animation.
[MyLoadingView hide] stops activityview animation and hides sharedInstanceView.
Please notice, that you need to invoke [MyLoadingView show] in a separate thread.
UI changes in Cocoa only take effect the next time your code returns control to the run loop. As long as your search task runs synchronously on the main thread, it will block execution, including UI updates.
You should execute the time-consuming task asynchronously in the background. There are a number of options for this (NSOperation, performSelectorInBackground:..., Grand Central Dispatch, ...).
Related
I have a navigation controller with a couple of view controllers in it. In the first view controller (the app's initial one) I perform an animation, which moves two UIImageViews around, and fades in some additional ui elements. Now, when I push the next view controller and then go back to the initial one, the transformations are gone and the image views are exactly where they were when loaded from the storyboard. The additional ui elements, however, are still visible and so is the text I've entered in some UITextFields. So the view controller is not entirely reset to its initial state but somehow the performed animations are undone. Can anybody tell me whether this is regular behavior and - if so - how I can preserve the transformations?
A little more disclosure: I'm simply setting off a regular animation upon hitting a button...
[UIView animateWithDuration:0.4
delay:0.0
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
self.firstView.center = firstCenter;
self.secondView.center = secondCenter;
}
completion:^(BOOL finished) {
// Upon complection fade in additional ui elements
[UIView animateWithDuration:0.4
animations:^{
self.firstElement.alpha = 1.0;
self.secondElement.alpha = 1.0;
self.thirdElement.alpha = 1.0;
}
completion:^(BOOL finished) {
[self.activityIndicator stopAnimating];
}];
}];
... and then pushing the next view controller after hitting another button.
[self.navigationController pushViewController:_nextViewController animated:YES];
As far as I can see I'm not doing anything funky here, so any help is appreciated.
I have 2 subviews on the stage (a splash screen, and the main screen) once the splash screen finishes playing its audio it calls a function called -(void)audioComplete which is suppose to fade out the splash screen, revealing the main screen. I can't seem to get that working.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[self.window addSubview:splashController.view];
[self.window addSubview:rootController.view];
[self.window makeKeyAndVisible];
return YES;
}
-(void)audioComplete{
NSLog(#"REMOVE FROM STAGE");
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.5];
[UIView setAnimationDelegate:splashController.view];
splashController.view.alpha = 0.0;
[UIView commitAnimations];
[splashController release];
}
If I add NSLog(#"%#",[splashController.view superview]); in the audioComplete function I get (null), but not when I add it to the didFinishLaunchingWithOptions function.
Apple highly recommends against using splash screens. Instead, you should use an image called 'Default.png' in the root directory. This will get displayed while the application is launching and make it appear that your application is faster than it actually is. Apple could potentially reject your submission to the app store if you create your own loading screen.
Could be one of two things:
1) Seems like your rootControllerView is being added on top of the splashControllerView.
Maybe your animation is happening but you can't see it as your rootControllerView is blocking it.
Try reversing your order of addSubview.
2) Don't release your splash view in the same method you're using for animation. Wait for the animation to finish before releasing your view. You can do this by:
-(void)audioComplete
{
NSLog(#"REMOVE FROM STAGE");
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.5];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidFinishSelector:#selector(releaseView)];
splashController.view.alpha = 0.0;
[UIView commitAnimations];
}
-(void)releaseView
{
[splashController release];
}
Also, as a best practice for memory management, don't release splashControllerView directly.
What I do is release a view immediately after adding it as a subview (adding a subview increases it's retain count).
When I'm done with the subview, I simply call [subView removeFromSuperView] which reduces the retain count and makes it zero.
Simply put:
UIView *view = [[UIView alloc] init]; //retain count = 1
[self.view addSubview:view]; //retain count = 2
[view release]; //retain count = 1
//do stuff with the view
[view removeFromSuperview]; //retain count = 0;
I don't really see your question, because in your didFinishLaunchingWithOptions you're adding your view and it's really there! So you will receive an answer every time you call NSLog(#"%#",[splashController.view superview]); but in audioComplete you release your subView and it's away... After this you can't get access to the superview, because - you're subView is released...
I would use this:
-(void)audioComplete{
NSLog(#"REMOVE FROM STAGE");
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.5];
[UIView setAnimationDelegate:splashController.view];
splashController.view.alpha = 0.0;
[UIView commitAnimations];
[splashController.view removeFromSuperview];
}
You'll get the same thing again, but you're subView is still there if you need something later.
For Fade in and Fade out images between splash and mainscreen. i suggest you could trying having two image in rootviewcontroller. made the fade in and fadeout animation between two UIIMageViews......
for fade in read out this sample tuts:
http://iosdevelopertips.com/user-interface/fade-transition-fade-images-in-and-out.html
during the animation, u can complete your audio streams
I am using UIView's animation features to swim one image of a fish onto the screen and when there already exists a fish in the middle of the screen, swim it off and swim a new one on. The selection of the fish is sent from a separate tableview controller.
To accomplish this, I m keeping two instance variables of UIImageView and exchanging self.fishViewCurrent for self.fishViewNext once the animation is completed (see code below).
My problem is when the user presses on new table cells quickly, before the next fish has had a chance to arrive. My quick solution is to delay a new message that will add the fish and just do nothing at the moment. This works for 2 quick touches, but not more. I am looking for ideas if anyone knows how I can improve the code below, so that the animation is smooth and you can click on whatever while the final displayed image is the one last clicked. Here re the important methods:
Creating the first image view:
- (void) createFish {
self.fishViewCurrent = [[[UIImageView alloc] init] autorelease];
self.fishViewCurrent.contentMode = UIViewContentModeScaleAspectFit;
self.fishViewCurrent.bounds = CGRectMake(0.0f, 0.0f, kFishWidth, kFishHeight);
self.fishViewCurrent.center = CGPointMake(-kFishWidth, kFishYPos); // off the screen to the left
[self addSubview:self.fishViewCurrent];
}
Public method called when a tableview cell is selected:
- (void) addFish:(UIImage *)fish {
if (self.fishViewNext) {
[self performSelector:#selector(addFish:) withObject:(UIImage *)fish afterDelay:1.0];
return;
}
self.fishViewNext = [[[UIImageView alloc] initWithImage:fish] autorelease];
self.fishViewNext.contentMode = UIViewContentModeScaleAspectFit;
self.fishViewNext.bounds = CGRectMake(0.0f, 0.0f, kFishWidth, kFishHeight);
self.fishViewNext.center = CGPointMake(self.bounds.size.width + kFishWidth, kFishYPos); // off the screen right
[self addSubview:self.fishViewNext];
[UIView beginAnimations:#"swimFish" context:nil];
[UIView setAnimationDuration:2.0f];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:#selector(fishSwamOffHandler:finished:context:)];
[UIView setAnimationRepeatAutoreverses:NO];
self.fishViewNext.center = CGPointMake(self.center.x, kFishYPos); // center
self.fishViewCurrent.center = CGPointMake(-kFishWidth, kFishYPos); // exit stage left
[UIView commitAnimations];
}
Handler to switch the 'next' fish (UIImageView) to the 'current':
- (void)fishSwamOffHandler:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context {
[self.fishViewCurrent removeFromSuperview];
self.fishViewCurrent = self.fishViewNext;
self.fishViewNext = nil;
}
Simplest thing would be to create an ivar BOOL, say _fishIsSwimming, and a _queuedFishView then change the beginning of addFish: to look like this:
- (void) addFish:(UIImage *)fish
{
// queue the most recent fish, if one was passed in
if(fish)
self.queuedFishView = [[[UIImageView alloc] initWithImage:fish] autorelease];
// If something is swimming, leave now
if(_fishIsSwimming)
return;
// If we get here and there's no queued fish, leave now
if(!self.queuedFishView)
return;
// We have a queued fish and nothing is swimming, so do the queued fish next
// and clear the queued fish
_fishIsSwimming = YES;
self.nextFishView = self.queuedFishView;
self.queuedFishView = nil;
// Then carry on as normal
}
Then in your animation completion callback, you do this:
-(void) fishSwamOffHandler:(NSString*)animationID finished:(NSNumber*)finished context:(void*)context
{
// We're done animating, so ready to do another swimming fish
_fishIsSwimming = NO;
[self.fishViewCurrent removeFromSuperview];
self.fishViewCurrent = self.fishViewNext;
self.fishViewNext = nil;
// now call to add another fish with nil, so that it will use
// the queued fish if there is one and otherwise just early out
[self addFish:nil];
}
So what will happen is that you'll always maintain the most recently passed in fish in the queuedFishView, and then as soon as the animation is over you can drop back into addFish, and if there's a fish waiting to go then you send it. If not you just wait for the next one sent by a button press
I'm trying to show a UIProgressView on top of a table view after certain user interactions. In my table view, if the user taps one particular cell, I slide up a UIView that contains a toolbar with bar items and a picker view (it very much behaves like an action sheet). When I do this, I'm adding the view offscreen to the current view, and then animating the slide-in. When the user makes a selection and taps "done", the view slides back out. At that point, the progress view is supposed to appear and update as some things are happening in the background.
The problem I'm having is that after the "alert sheet" UIView is slided out of the current view, nothing happens in the UI for a good while. When I show the sliding view, I do this:
[[[UIApplication sharedApploication] keyWindow] addSubview:slidingView];
CGRect newFrame = CGRectMake(...);
[UIView animateWithDuration:0.3
animations:^{
slidingView.frame = newFrame;
}];
When the user taps the 'done' button in the sliding view, this action method is invoked:
- (void) done {
NSNumber *row = GetSelectedRowSomehow();
[self dismiss:#selector(doneCallback:) withObject:row];
}
- (void) dismiss:(SEL)cb withObject:(id)obj {
[UIView animateWithDuration:0.3
animations:^{
slidingView.frame = CGRectMake(...);
}
completion:^(BOOL finished) {
[self.delegate performSelectorOnMainThread:cb
withObject:obj
waitUntilDone:NO];
[slidingView performSelectorOnMainThread:#selector(removeFromSuperview:)
withObject:nil
waitUntilDone:NO];
}];
}
The callback that gets called here:
- (void) doneCallback {
self.dialogView.hidden = NO;
self.progressView.progress = 0;
for (float i = 0; i < 1.0; i += 0.1) {
self.progressView.progress += i;
sleep(0.5);
}
}
In my case, dialogView doesn't appear until after callback has completed. Why wouldn't it just update the display immediately after its hidden property was set to NO?
The display is updated in the main thread once your code finishes executing, so by blocking the main thread you're preventing that happening.
One reason for that is that the updates are batched together so they can be redrawn efficiently.
One solution/workaround would be to use performSelector:afterDelay:0.01 to in doneCallback after the hidden=NO, moving the following code into a new selector that runs after the delay.
Alternatively you could run the code within "doneCallback" as a background operation instead (though you cannot directly update UIKit from the background thread, so you'd have to send messages to the main thread for any display updates).
Okay, this is the code:
[lblMessage setText: txtEnter.text];
[lblMessage sizeToFit];
scrollingTextView.contentSize = lblMessage.frame.size;
float width = (lblMessage.frame.size.width) + (480);
[UIView beginAnimations:#"pan" context:nil];
[UIView setAnimationDuration:durationValue];
[UIView setAnimationRepeatCount:5];
scrollingTextView.contentOffset = CGPointMake(width,0);
[UIView commitAnimations];
//The scrolling text view is rotated.
scrollingTextView.transform = CGAffineTransformMakeRotation (3.14/2);
[self.navigationController setNavigationBarHidden:YES];
btnChange.transform = CGAffineTransformMakeRotation (3.14/2);
I have the user enter in some text, press a button and then a label is replaced with the text, turned 90 degrees in a scrollview on a page.
After a certain number of characters, for example say 20.. the animation just won't load. I can go back down until the animation will run.
Any ideas on where I am going wrong, or a better way of storing the text etc etc ?
Core Animation animations are performed on a separate thread. When you enclose the change in contentOffset in a beginAnimations / commitAnimations block, that change will be animated gradually. The scrolling text view rotation that occurs next, outside of the animation block, will be performed instantly. Since both are interacting with the same control on different threads, it's not surprising that you're getting weird behavior.
If you want to animate the rotation of the text in the same way as the contentOffset, move that line of code to within the animation block.
If you want to have the rotation occur after the offset change animation has completed, set up a callback delegate method. You can use code in the beginning of your animation block similar to the following:
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:#selector(contentOffsetAnimationHasFinished:finished:context:)];
which requires you to implement a delegate method like the following:
- (void)contentOffsetAnimationHasFinished:(NSString *)animationID finished:(BOOL)finished context:(void *)context;
{
// Do what you need to, now that the first animation has completed
}
EDIT (2/6/2009):
I just created a simplified version of your application, using only the sideways text scrolling, and find no problem with the animation on the device with any number of characters. I removed all extraneous calls to layout the buttons, etc., and only animate the text. Rather than apply the rotation transform to the scroll view every time you click the button, I have it start rotated and stay that way.
I thought it might be a layer size issue, as the iPhone has a 1024 x 1024 texture size limit (after which you need to use a CATiledLayer to back your UIView), but I was able to lay out text wider than 1024 pixels and still have this work.
A full Xcode project demonstrating this can be downloaded here. I don't know what your issue is, but it's not with the text animating code you present here.
Right, this code is working fine in the simulator, and works fine until i enter more than say 20 characters in txtEnter.text:
- (IBAction)updateMessage:(id)sender
{
//Animation coding
//Put the message in a resize the label
[lblMessage setText: txtEnter.text];
[lblMessage sizeToFit];
//Resize the scrolliew and change the width.
scrollingTextView.contentSize = lblMessage.frame.size;
float width = (lblMessage.frame.size.width) + (480);
scrollingTextView.transform = CGAffineTransformMakeRotation (3.14/2);
//Begin the animations
[UIView beginAnimations:#"pan" context:nil];
[UIView setAnimationDuration:durationValue];
[UIView setAnimationRepeatCount:5];
//Start the scrolling text view to go across the screen
scrollingTextView.contentOffset = CGPointMake(width,0);
[UIView commitAnimations];
//General hiding and showing points.
[txtEnter resignFirstResponder];
[btnChange setHidden:NO];
[txtEnter setHidden:YES];
[btnUpdate setHidden:YES];
[lblSpeed setHidden:YES];
[lblBackground setHidden:YES];
[backgroundColourControl setHidden:YES];
[speedSlider setHidden:YES];
[scrollingTextView setHidden:NO];
[backgroundImg setHidden:NO];
[toolbar setHidden:YES];
[self.navigationController setNavigationBarHidden:YES animated:YES];
//Depending on the choice from the segment control, different colours are loaded
switch([backgroundColourControl selectedSegmentIndex] + 1)
{
case 1:
[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleBlackTranslucent animated:YES];
break;
case 2:
[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleBlackOpaque animated:YES];
break;
default: break;
}
btnChange.transform = CGAffineTransformMakeRotation (3.14/2);
}
I've tried your method Brad, but can't seem to get the (void) section to work properly.
What my app does its fill the label with a message and then rotates them all to act like it's in landscape mode. Then what it does it scroll the label within a scrollview to act like a scrolling message across the screen.