UIActivityIndicatorView weird behavior when stopping and starting - ios

I have a UIViewController and in its respective storyboard scene I have an activity indicator connect to the controller like this:
#IBOutlet weak var activityIndicator: UIActivityIndicatorView!
I want to hide this activity indicator when the view is first launched, then after the users presses a button, inside its #IBAction, I want to start animating the indicator. So I wrote this:
override func viewDidLoad() {
super.viewDidLoad()
activityIndicator.stopAnimating()
}
and in my #IBAction I have:
#IBAction func attemptLogin(sender: UIButton) {
// Start the activity indicator
activityIndicator.startAnimating()
}
But this code does not work. The screen is launched without showing the activity indicator but when the button is pressed, it does not start animating and it does not even appear.
I have tried setting the .hidden property of this object but didn't get anywhere. I do not really want to add this indicator programmatically. Is there a way to make it work?

Try out dynamically create UIActivityIndicatorView
UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
spinner.frame = CGRectMake(0, 0, 24, 24);
[spinner startAnimating];
[spinner release];

There's a couple of things you could consider here as the root of your problems:
The activity indicator is not attached to the IBOutlet
The button's target is not attached to the IBAction (put a breakpoint in your method to be sure)
The default state of an activity indicator is not animated, so your line: activityIndicator.stopAnimating() is redundant.
Depending on your view's layouts/constraints, the UIActivityIndicator might be outside of your screens frame. Try printing out the activity indicators frame with NSLog(#"%#", NSStringFromCGRect(activityIndicator.frame)) before you start the animation.
Hope this helps!

Related

Subview / Loading indicator not visible

Mission: Generic Loader
Created a generic LoadingViewController to handle loading and display a loading indicator from any UIView (see photo).
Issue: Not always displayed
The indicator is not displayed if the user clicks the refresh button (top left) which immediately downloads relevant data and should display the loading indicator in the meantime.
However if the user clicks the filter icon (top right) and presented another UIView (push) and selects a few options and is delegated back to the mapView the loader indicator displays as expected - during loading data and is then removed.
The frame for the subView holding the loadingIndicator seems fine (0,0, 375, 667) in both instances. What could be at play?
Refresh Button
#IBAction func refreshPlaces(sender: AnyObject) {
downloadStores = true
updateStores()
}
Delegate Function from Filter
func filterViewDidFinish(controller: BudgetTableViewController, download: Bool) {
// Dismiss modal
self.navigationController!.popToRootViewControllerAnimated(true) // Pop back to map
// Update stores
downloadStores = download
updateStores()
}
Update Stores
func updateStores(){
// Display loading indicator
println("Display loading indicator")
let subViewController = self.storyboard?.instantiateViewControllerWithIdentifier("LoadingViewController") as! LoadingViewController
let loadingSubView: UIView = subViewController.view
loadingSubView.tag = 1 // Unique identifier
// Add loading indicator
self.navigationController!.view.addSubview(loadingSubView)
// Verify frame is OK
println("Frame: \(self.navigationController!.view.viewWithTag(1)?.frame)")
// Async
dispatch_async(dispatch_get_main_queue(), {
// Update location
subViewController.status.text = "Updating your location"
self.getCoordinates()
// Get stores
if self.downloadStores == true {
subViewController.status.text = "Finding stores nearby"
self.getStores()
}
// Create markers
self.googleMapsMarkers()
// Dismiss loading indicator
println("Remove loading indicator")
self.navigationController!.view.viewWithTag(1)?.removeFromSuperview()
})
}
UPDATE
The Loading Indicator gets displayed also on the 'refresh button' if I remove the self.navigationController!.view.viewWithTag(1)?.removeFromSuperview().
Question is WHY is it being removed before the data is downloaded (i.e. the functions higher in the Async que are finished)?
I understood the async que as that things operate in the background until they are done, and that a function entered into the que should only be executed when a previous function in the que is done - but perhaps I am mistaken?
New to swift... :)
You should not be adding views directly into your navigation controller's view. If you want to present a child view controller that overlays over your current view you should do it correctly by calling the right api methods:
subViewController.view.frame = self.view.bounds;
[self.view addSubview:subViewController.view];
[self addChildViewController:subViewController];
[subViewController didMoveToParentViewController:self];
Maybe this might be helpful for you. Have a look at UIView extension where activity indicator is added to view

Hide activity indicator

On my main storyboard I created an Activity Indicator.
I want to hide my activity indicator until the button has been pressed.
Is there a way I can do that?
When I press the button the activity indicator starts animating.
self.indicator.hidden = NO;
[self.indicator startAnimating];
[self performSelector:#selector(showData) withObject:nil afterDelay:2.0f];
Can I hide the activity indicator until the button has been pressed, and then show this activity indicator?
Select the Activity Indicator in Storyboard, then select property "Hides when stopped". This way you don't have to hide it, just start or stop the animation, and the Activity Indicator will show and hide automatically. Of course, you still have to add the code to start and stop the animation to buttons.
Swift 3 Xcode 8.3.2
First hide your activityIndicator in viewDidLoad() method and set hidesWhenStopped property to true.
override func viewDidLoad(){
super.viewDidLoad()
self.activityIndicator.isHidden = true
self.activityIndicator.hidesWhenStopped = true
}
Later when you want to show activityIndicator :
self.activityIndicator.isHidden = false
self.activityIndicator.startAnimating()
And when you want to stop it use :
self.activityIndicator.stopAnimating()
Yes, you can select Hidden property in the Storyboard, and change it in your button action method when you tap it. But you can just select Hides when Stopped and your activity will be hidden if not animating and show up otherwise.
You can add
self.indicator.hidden = YES;
to your UIViewController's viewDidLoad method or select Hidden checkmark in Storyboard.
if you are using swift then you can do it when you set the outlet of your indicator, like-
#IBOutlet weak var indicator:UIActivityIndicatorView!{
didSet{
indicator.hidesWhenStopped = true
}
}
Basically what it meant is, set my activity indicator outlet's hidesWhenStopped property to true when it is established.

Animate UITextField

I need to make it so that when I click a button, the UITextField transforms left onto the view from outside the view. However, when I execute the following code, the UITextField starts off in the middle of the viewcontroller, and then when the button is clicked it transforms left onto the view from outside the view. How can I make it so that when the view loads initially, it is not seen until the button is clicked, using swift.
#IBAction func joinCircleButton(sender: AnyObject) {
let button = sender as UIButton
joinTextField.frame.origin.x=500
joinTextField.frame.origin.y=100
if (button.frame.origin.x - 75>0){
UIView.animateWithDuration(0.5, animations:{
button.frame = CGRectMake(button.frame.origin.x - 125, button.frame.origin.y,button.frame.size.width, button.frame.size.height)
button.transform = CGAffineTransformMakeScale(0.5, 0.5);
self.joinTextField.frame=CGRectMake(self.joinTextField.frame.origin.x - 325, self.joinTextField.frame.origin.y,self.joinTextField.frame.size.width, self.joinTextField.frame.size.height)
})
}
}
You need to put the UITextField, or any object that you wish to hide, outside of the view by using a function called:
func viewDidLayoutSubviews()
This function is called just before the screen is loaded. It gets the sub-views ready but doesn't show them on the screen yet. So this is an opportunity to hide the UITextField from sight like so:
func viewDidLayoutSubviews(){
// Here is just an EXAMPLE of what I'd do with my text field, you can change this however you wish
// The point is that I am putting it away so no one can see it at first, and then later it will show
joinTextField.center = CGPointMake(joinTextField.frame.origin.x-500, joinTextField.frame.origin.y)
}
Now the textfield should be hidden when the view loads and then it can be animated later on.I hope this helped.

Adding an activity indicator to the nav bar

I have a view controller that starts an async network request when the user taps a button, then pushes another controller after the request finishes. In that intervening time, I want the navigation bar to display an activity indicator, so the user knows it's fetching data. I have this code in the IBAction for the button:
self.activityIndicator = [[UIActivityIndicatorView alloc]
initWithActivityIndicatorStyle: UIActivityIndicatorViewStyleWhite];
// I've tried Gray, White, and WhiteLarge
[self.activityIndicator startAnimating];
self.activityIndicator.hidden = NO;
UIBarButtonItem* spinner = [[UIBarButtonItem alloc] initWithCustomView: self.activityIndicator];
self.navigationItem.rightBarButtonItem = spinner;
The indicator does not appear, but this code is getting called. I've looked at other posts on the topic, and they say to do exactly this. The UI thread does not appear blocked during the call (I can tap other buttons and navigate a different path while waiting).
Am I missing something?
I solved it.
There was a cancel button there already that was going away when the keyboard did. The code that removed the cancel button didn't care whether it was removing a button or my spinner. I moved the spinner code to a later location, and all is good.
I have had trouble displaying a UIBarButtonItem if one already existed in that place. If this is the case try:
self.navigationItem.rightBarButtonItem = nil;
self.navigationItem.rightBarButtonItem = spinner;
You can also try to set the bar button item like this (if you target at least iOS 5):
self.navigationItem.rightBarButtonItems = #[spinner];

UITableView Activity Indicator the Apple way

My problem is pretty common. I want to show some "loading" stuff while the data that will be shown in a table is being downloaded. But all the solutions I've seen this far is either to add some spinners to cells, or put some semi-transparent view over the app.
What I want to active is the thing that apple does in its apps (for example YouTube). Like, the spinning wheel with word "Loading" is shown on the white background, and then the data is shown.
I have also seen some solution with a grouped style, but my table has a plain style so it isn't helping me a lot I think.
EDIT
My final code looks like this:
- (void)loadImagesData
{
UIView *tableView = self.view;
self.view = [[LoadingView alloc] initWithFrame:CGRectMake(0, 0, 320, 367)];
dispatch_queue_t downloadQueue = dispatch_queue_create("flickr downloader", NULL);
dispatch_async(downloadQueue, ^{
NSArray *images = [FlickrFetcher photosInPlace:self.place maxResults:MAX_PHOTOS];
dispatch_async(dispatch_get_main_queue(), ^{
self.view=tableView;
self.imagesData = images;
});
});
dispatch_release(downloadQueue);
}
and imageData setter calls reloadData on table view.
Create a UIViewController (not a UITableViewController - even if you are about to present a table)
Create your "Loading" view and add it as an outlet
Create your tableview and add it as an outlet too
When the controller is presented set its view property to point to your "Loading" view. When all tasks have completed, switch the view with the tableview
Just don't forget to add protocols and methods involved in order to control your tableview
I hope that this will help you
Another answer to show an activity indicator inside a UITableViewController, without changing it to a UIViewController.
In the storyboard, add an activity indicator inside the table view (but not above the TableViewCell !). It will be inside the table view header (not section header !)
Connect the outlet of the activity indicator to your class file
#IBOutlet weak var activityIndicator: UIActivityIndicatorView!
Use these functions to show or hide the activity indicator
// enable tableview and hide the activity indicator
func activateScreen() {
self.tableView.tableHeaderView?.frame = CGRect(x: 0, y: 0, width: tableView.frame.width, height: 0)
self.tableView.tableHeaderView = self.tableView.tableHeaderView // necessary to really set the frame
activityIndicator.stopAnimating()
}
// disable tableview animate the activity indicator
func disactivateScreen() {
self.tableView.tableHeaderView?.frame = tableView.frame
self.tableView.tableHeaderView = self.tableView.tableHeaderView // necessary to really set the frame
activityIndicator.startAnimating()
}
Enjoy !

Resources