Animate Spinner when loading new page - objective-c

I have a UITableView which from an external RSS feed.
When you select a row it uses navigationController and slides in from the right, the problem is that the RSS feed contains images therefore it can can take a few seconds to load and without any indication of what is going on you can mistake it for an application crash.
I decided to add a spinner so that you know that new page is loading.
Here is my code:
RootViewController.m
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"Loading New Page");
[tableView deselectRowAtIndexPath:indexPath animated:YES];
DetailsViewController *detailViewController = [[DetailsViewController alloc] initWithNibName:#"DetailsViewController" bundle:nil];
detailViewController.item = [rssItems objectAtIndex:floor(indexPath.row/2)];
[self.navigationController pushViewController:detailViewController animated:YES];
[detailViewController release];
UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
spinner.center = CGPointMake(160, 240);
[self.view addSubview:spinner];
[spinner startAnimating];
[spinner release];
}
DetailsViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
NSString *imgURL = [item objectForKey:#"image"];
NSData *mydata = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:imgURL]];
item_photo.image = [[UIImage alloc] initWithData:mydata];
item_title.text = [item objectForKey:#"title"];
item_date.text = [NSString stringWithFormat:#"Date: %#",[item objectForKey:#"date"]];
item_time.text = [NSString stringWithFormat:#"Time: %#",[item objectForKey:#"time"]];
item_cost.text = [NSString stringWithFormat:#"Cost: £%#",[item objectForKey:#"cost"]];
item_info.text = [item objectForKey:#"description"];
self.navigationItem.title = #"Event Type";
}
There are two problems with this code.
The Spinner does not active until after the new page has loaded.
The Spinner does not disable once loaded.
If anyone could help me with this problem i would be truly gratefully.

You are adding the activity indicator view to the view of the controller which is pushing the detail view controller, so you wont see it anyway
try moving the second group of code to the viewDidLoad method of DetailsViewController, you can call stopAnimating on the activity indicator when you are finished loading. To get a reference to the UIActivityIndicator you should add a tag
e.g. in viewDidLoad
UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
spinner.center = CGPointMake(160, 240);
spinner.tag = 12;
[self.view addSubview:spinner];
[spinner startAnimating];
[spinner release];
in the loadingFinished method (whichever method is called when finished loading)
[[self.view viewWithTag:12] stopAnimating];

You need to do some work in a background thread. If the following line is the one that takes the time:
detailViewController.item = [rssItems objectAtIndex:floor(indexPath.row/2)];
Then you could do this in the background with GCD:
UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// This is the operation that blocks the main thread, so we execute it in a background thread
id item = [rssItems objectAtIndex:floor(indexPath.row/2)];
// UIKit calls need to be made on the main thread, so re-dispatch there
dispatch_async(dispatch_get_main_queue(), ^{
detailViewController.item = item;
[spinner stopAnimating];
});
});
And +1 to #wattson12 - you need to add the spinner to the new view instead. Alternatively you could add the spinner to the current view, and instead put the pushViewControllercall into your GCD main-queue block.
Final point - you'll want to remove the spinner from its superview once you stop it animating. Alternatively, you can have a single instance of the spinner, and set hidesWhenStopped to YES.

This is a spinning wheel over a blurred view in swift:
func blurScence(){
let blurEffect: UIBlurEffect = UIBlurEffect(style: .Dark)
let blurView: UIVisualEffectView = UIVisualEffectView(effect: blurEffect)
blurView.translatesAutoresizingMaskIntoConstraints = false
blurView.frame = self.view.frame
let spinner = UIActivityIndicatorView(activityIndicatorStyle:.White)
spinner.center=blurView.center
blurView.addSubview(spinner)
spinner.startAnimating()
self.view.addSubview(blurView)
}

Related

Activity indicator loading before Wkwebview in objective c

i am using wkwebview and loading an url in objective c. i need to show activity indicator before loading wkwebview. is there any delegate methods for wkwebview?
-(void)viewWillAppear:(BOOL)animated{
UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
[spinner setCenter:CGPointMake(160,124)];
[self.view addSubview:spinner];
spinner.tag = 100;
[spinner startAnimating];
_homeWebView.opaque = true;
// http://ispatialtec.com/birdathon.php
_homeWebView.userInteractionEnabled = true;
[_homeWebView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.adbirdathon.ae/Birdathon/index.html"]]];
[spinner stopAnimating];
}
Yes there is the WKNavigationDelegate method
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation
So remove your spinner there.

Add button horizontally to Page Control

I need to add a button horizontally to a Page Control in UIPageViewController. I have added my pageviewcontroller as a subview to a ViewController, by default im getting the page control .Now I wanted to add a button, Can anyone suggest any way to add button ?
Thanks in advance!!
_pageViewController = [[UIPageViewController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStyleScroll
navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal
options:nil];
self.view.backgroundColor = [[UIColor blackColor]colorWithAlphaComponent:0.5];
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:
#"Main" bundle:[NSBundle mainBundle]];
UIViewController *p1 = [storyboard instantiateViewControllerWithIdentifier:#"First"];
UIViewController *p2 = [storyboard
instantiateViewControllerWithIdentifier:#"Second"];
UIViewController *p3 = [storyboard
instantiateViewControllerWithIdentifier:#"Third"];
UIViewController *p4 = [storyboard
instantiateViewControllerWithIdentifier:#"Fourth"];
_Array = #[p1,p2,p3,p4];
_pageViewController.dataSource = self;
_pageViewController.delegate = self;
[_pageViewController setViewControllers:#[_Array[0]]
direction:UIPageViewControllerNavigationDirectionForward
animated:NO
completion:nil];
[_pageViewController willMoveToParentViewController:self];
[self addChildViewController:_pageViewController];
[self.view addSubview:_pageViewController.view];
[self.view sendSubviewToBack:self.pageViewController.view];
[_pageViewController didMoveToParentViewController:self];
I believe this is what youre looking for. He explains in detail how to add the buttons "on top of " your page view controller view. You can do this after you add the page view controller, you can use the sendSubviewToBack method to ensure that the buttons remain in the foreground while you will be able to swipe between the different view controllers of your page view controller. Have a look here
you can extract the view where page control is kept on, then you can do what ever you want. Below is the code for extracing the pagecontrol view:
NSArray *subviews = self.pageController.view.subviews;
for (int i=0; i<[subviews count]; i++) {
if ([[subviews objectAtIndex:i] isKindOfClass:[UIPageControl class]]) {
self.pageControl = (UIPageControl *)[subviews objectAtIndex:i];
}
}
UIView *pageControlBaseView = [[UIView alloc] initWithFrame:CGRectMake(0,60,100, 40)];
[pageControlBaseView addSubview:self.pageControl];
self.pageControl.pageIndicatorTintColor = [UIColor redcolor];
self.pageControl.currentPageIndicatorTintColor = [UIColor greencolor];
[self.view addSubview:pageControlBaseView];
please let me know if you need more clarity.

Objective-C: UILabel "stuck" in my view

I'm working on a tabbed app that (among other things) creates a simple Facebook feed. The Facebook section is a UINavigationController holding a Master Table View and a Detail View. When I click one of the rows in the Master Table View, it brings up the detail view (and the appropriately filled labels) as intended. However, if I go back to the Master View and select another row, something strange happens. If the "detailLab" label (which is sizeToFit'd) was, say, four lines long on the first occurrence of Detail View, and the "detailLab" label was 2 lines long on the second occurrence of Detail View, then on the second occurrence of Detail View, "detailLab" will display the first two lines properly, but underneath them will be the last two lines from the first occurrence of Detail View.
A picture of this happening.
And some (hopefully) relevant code:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
CGRect labelFrame = CGRectMake(100, 40, 220, 150);
detailLab = [[UILabel alloc] initWithFrame:labelFrame];
[detailLab setTextColor:[UIColor darkGrayColor]];
detailLab.font = [UIFont systemFontOfSize:14];
[detailLab setText:detail];
[detailLab setNumberOfLines:0];
[detailLab sizeToFit];
[self.view addSubview:detailLab];
NSURL *url = [NSURL URLWithString:pictureString];
NSData *imageData = [[NSData alloc] initWithContentsOfURL:url];
picture = [UIImage imageWithData:imageData];
bigImage.image = picture;
NSLog(#"dateString: %#", dateString);
dateLabel.text = dateString;
}
- (void)viewDidUnload
{
[super viewDidUnload];
bigImage = nil;
detailLab = nil;
detailLab.hidden = YES;
// Release any retained subviews of the main view.
self.detailDescriptionLabel = nil;
}
If you need any other code or details, just ask; I'd be happy to provide it.
The problem is that each time viewWillAppear is called you are creating a new label without, I assume, removing it in viewWillDisappear. So the labels are stacking up, one on top of the other.
Remember you may get called like this (ignoring irrelevant calls):
viewDidLoad
viewWillAppear
viewWillDisappear
viewWillAppear <- Whoops! Two labels
viweWillDisappear
viewDidUnload
A neater solution would be to add your label view in viewDidLoad and only alter the position of that already existing view in viewWillAppear. You still need to remember to dispose of the view in viewDidUnLoad.
Here's what I mean (note this was only eyeball compiled so you may need to correct some typos):
- (void)viewDidLoad
{
[super viewDidLoad];
detailLab = [[UILabel alloc] initWithFrame:CGRectZero];
[detailLab setTextColor:[UIColor darkGrayColor]];
detailLab.font = [UIFont systemFontOfSize:14];
[self.view addSubview:detailLab];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
CGRect labelFrame = CGRectMake(100, 40, 220, 150);
[detailLab setFrame:labelFrame];
[detailLab setText:detail];
[detailLab setNumberOfLines:0];
[detailLab sizeToFit];
NSURL *url = [NSURL URLWithString:pictureString];
NSData *imageData = [[NSData alloc] initWithContentsOfURL:url];
picture = [UIImage imageWithData:imageData];
bigImage.image = picture;
NSLog(#"dateString: %#", dateString);
dateLabel.text = dateString;
}
- (void)viewDidUnload
{
[super viewDidUnload];
bigImage = nil;
detailLab = nil;
detailLab.hidden = YES;
// Release any retained subviews of the main view.
self.detailDescriptionLabel = nil;
}
That is normal with Master View to Detail Views - Frustrating, but Normal. The solution I found when I had the problem was to reset the label at the top of each -ViewDidLoad.
One option would be to remove it in viewDidUnload. Another would be to reset the label each time, using a segment of code like the one below, positioned above the section of your code where it actually loads in the content
detailLab = [NSString stringWithFormat:#""];
The issue is that with the setup of most Master/Detail views, it's not actually creating new detail views each time you add a view, it's just creating another link to the same view in the master view section. This is fine, but you just need to remember to use viewDidUnload lots
Hope it Helps

Navigation Control Problem

I am using some methods to load a new .xib and go back to the main menu. However after about five time it crashes by using too much memory. I need to be able to go back to the main menu and to the game many times. Any other methods I should use for the navigation controls.
Main Menu part:
GameViewController* game = [[GameViewController alloc initWithNibName:#"GameViewController" bundle:nil];
[self.navigationController pushViewController:game animated:NO];
Game part to return to main menu:
[self.navigationController popViewControllerAnimated:NO];
Here is the viewdidLoad
{
- (void)viewDidLoad {
[super viewDidLoad];
[self StartTimer];
TotalSeconds = 0;
GameCenterTotalSeconds = 0;
timeSec = 0;
timeMin = 0;
Background = [[[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 480, 320)] autorelease];
Background.image = [UIImage imageWithContentsOfFile:[ [ NSBundle mainBundle] pathForResource:#"Background" ofType:#"png"]];
[self.view addSubview:Background];
timeLabel.textColor = [UIColor redColor];
[self.view addSubview:timeLabel];
NumberLabel = [[[UIImageView alloc] initWithFrame:CGRectMake(0, -4, 60, 70)] autorelease];
NumberLabel.image = [UIImage imageWithContentsOfFile:[[ NSBundle mainBundle] pathForResource:#"Number" ofType:#"png"]];
[self.view addSubview:NumberLabel];
QuestionNumber = [[[UILabel alloc] initWithFrame:CGRectMake(23, 17, 20, 20)] autorelease];
QuestionNumber.text = #"1";
QuestionNumber.textColor = [UIColor redColor];
QuestionNumber.backgroundColor = [UIColor clearColor];
[QuestionNumber setFont:[UIFont fontWithName:#"Marker Felt" size:30]];
[self.view addSubview:QuestionNumber];
numberLives = 1;
appDelegate = (OppositeMoronTestAppDelegate *)[[UIApplication sharedApplication]delegate];
musicButton = [[[UIButton buttonWithType:UIButtonTypeCustom] retain] autorelease];
musicButton.frame = CGRectMake(5, 283, 35, 35);
musicButton.backgroundColor = [UIColor clearColor];
if (appDelegate.shouldPlayMusic == YES) {
UIImage *Image = [UIImage imageWithContentsOfFile:[[ NSBundle mainBundle] pathForResource:#"MusicOn" ofType:#"png"]];
[musicButton setBackgroundImage:Image forState:UIControlStateNormal];
[musicButton addTarget:self action:#selector(TurnMusicOff) forControlEvents:UIControlEventTouchUpInside];
} else {
UIImage *Image = [UIImage imageWithContentsOfFile:[[ NSBundle mainBundle] pathForResource:#"MusicOff" ofType:#"png"]];
[musicButton setBackgroundImage:Image forState:UIControlStateNormal];
[musicButton addTarget:self action:#selector(TurnMusicOn) forControlEvents:UIControlEventTouchUpInside];
}
[self.view addSubview:musicButton];
[self showQuestion1];
}
}
try autorelease on your view controller:
GameViewController* game = [[[GameViewController alloc initWithNibName:#"GameViewController" bundle:nil] autorelease];
The navigation controller will take ownership of the view controller passed to it, so you don't have to keep a reference to it. But you can't keep allocating GameViewControllers over and over without releasing them. autorelease is useful for that. You could also release it after you've passed it to the navigation controller if you prefer:
GameViewController* game = [[GameViewController alloc initWithNibName:#"GameViewController" bundle:nil];
[self.navigationController pushViewController:game animated:NO];
[game release];
game = nil;
EDIT:
So if you're already releasing the game object, then it must be a memory leak within the GameViewController class itself.
Annything you alloc, copy or retain in your GameViewController class you're supposed to release in the dealloc method (and maybe also in the viewDidUnload method if you're alloc/copy/retaining in the viewDidLoad method).
The iOS Memory Management Programming Guide might be helpful if you want to get into more detail.
If you want to post the relevant code from the GameViewController class, I'm sure someone will be able to help you pin down the memory leak.
You can also try the Leaks tool in Instruments
EDIT 2:
I'm assuming you have several IBOutlets connected to properties in your GameViewController class...
don't know if you're already doing this, but in your viewDidUnload method AND on your dealloc method you have to set all of these IBOutlets properties to nil in order to release them, like so:
- viewDidUnload
{
//... whatever comes before
self.timeLabel = nil;
self.NumberLabel = nil;
//... etc
}
- dealloc
{
//... whatever comes before
self.timeLabel = nil;
self.NumberLabel = nil;
//... etc
[super dealloc];
}
In general, if you have any properties declared with retain, that means that when you set that property the object will be retained. If you set that property to nil, the object that was there will be released for you. So any properties with the retain keyword should be set to nil (or the backing ivar released).

long delay when calling cocoa method

I have a method for saving the contents of a UIScrollView with a user-supplied filename.
Everything works fine, except that there is a long delay when the user taps the "Save" button and the method is called. I can't work out what's calling the delay, nor find a way to indicate to the user that everything is ok, we have not crashed!
I thought the delay was occurring during the renderInContext, but it seems to be happening a lot earlier, when there is not much else going on.
Here is the troublesome method:
- (void)captureViewImage {
NSLog(#"captureViewImage called!");
// long delay happens here!
fileNamer.title = #"Preparing to save...";
// get user's file name
NSString *fileName = fileNamer.fileNameField.text;
// dismiss keyboard
[fileNamer.fileNameField resignFirstResponder];
// dismiss modal view
[self dismissFileNamingFormSheet];
CGRect oldFrame = mainScrollView.frame;
// capture off-screen content
mainScrollView.frame = CGRectMake(0, 0, 1024, 1432);
// make screenshot
UIGraphicsBeginImageContext(mainScrollView.bounds.size);
[mainScrollView.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *screenImg = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// save screenshot in docs dir
NSData *pngData = UIImagePNGRepresentation(screenImg);
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDir = [paths objectAtIndex:0];
[pngData writeToFile:[documentsDir stringByAppendingPathComponent:fileName]
options:NSDataWritingAtomic error:nil];
// revert scroll view
mainScrollView.frame = oldFrame;
}
fileNamer is a custom class that throws up a UIModalPresentationFormSheet asking the user to supply a name for the file. It looks like this:
#implementation FileNamingViewController
#synthesize fileNameField, newFileName;
- (id)initWithNibName:(NSString *)nibNameOrNil
bundle:(NSBundle *)nibBundleOrNil parent:(TestDriveViewController *) myParent {
if (self == [super initWithNibName:#"FileNamingViewController" bundle:nil]) {
UIBarButtonItem *rightButton = [[UIBarButtonItem alloc]
initWithTitle:#"Save"
style:UIBarButtonItemStyleDone
target:myParent
action:#selector(captureViewImage)];
self.navigationItem.rightBarButtonItem = rightButton;
[rightButton release];
UIBarButtonItem *leftButton = [[UIBarButtonItem alloc]
initWithTitle:#"Cancel"
style:UIBarButtonItemStyleBordered
target:myParent
action:#selector(dismissFileNamingFormSheet)];
self.navigationItem.leftBarButtonItem = leftButton;
[leftButton release];
self.title = #"Save As?";
}
return self;
}
// UITextFieldDelegate
- (void)textFieldDidEndEditing:(UITextField *)textField {
[fileNameField resignFirstResponder];
}
- (void)viewDidLoad {
[fileNameField becomeFirstResponder];
[super viewDidLoad];
}
fileNamer is initialized and released as follows:
- (void)presentFileNamingFormSheet {
fileNamer = [[FileNamingViewController alloc]
initWithNibName:nil
bundle:nil
parent:self];
fileNamingNavCtrl = [[UINavigationController alloc]
initWithRootViewController:fileNamer];
fileNamingNavCtrl.modalPresentationStyle = UIModalPresentationFormSheet;
[self presentModalViewController:fileNamingNavCtrl
animated:YES];
// resize modal form sheet
fileNamingNavCtrl.view.superview.frame = CGRectMake(0, 0, 540, 115);
// reposition modal form sheet
CGPoint position = CGPointMake(self.view.center.x, self.view.center.y - 50);
fileNamingNavCtrl.view.superview.center = position;
}
- (void)dismissFileNamingFormSheet {
[fileNamer release];
[fileNamingNavCtrl release];
[self dismissModalViewControllerAnimated:YES];
}
Output from Time Profiler:
Running (Self) Symbol Name
1109.0ms 37.0% argb32_image_mark_rgb32
328.0ms 10.9% blkclr
171.0ms 5.7% lo_alltraps
134.0ms 4.4% pmap_enter
116.0ms 3.8% png_write_find_filter
102.0ms 3.4% pmap_remove_range
55.0ms 1.8% pmap_get_mapwindow
47.0ms 1.5% vm_page_lookup
47.0ms 1.5% ml_set_interrupts_enabled
43.0ms 1.4% vm_page_grab
38.0ms 1.2% OSAddAtomic64
34.0ms 1.1% hw_lock_to
31.0ms 1.0% alphaProviderGetBytes
30.0ms 1.0% hw_lock_unlock
26.0ms 0.8% png_read_filter_row
25.0ms 0.8% deflateInit_
23.0ms 0.7% vm_map_lookup_entry
23.0ms 0.7% adler32
22.0ms 0.7% memory_object_recover_named
I found out what was causing this. The image I was saving contained a lot of transparent views. For example, I had a lot of UIButtons which had an alpha value of 0.05 to make them more or less disappear. I didn't realise I could just set the button type to custom to make it invisible (I created the view along time ago). Once I set everything in my image view to have an alpha value of 1.0, the saving process became a lot quicker.