Show UIActivityIndicator during segue navigation, after UIBarButtonItem press - objective-c

I have a UIBarButtonItem in the navigation bar which switches to another screen, using a segue. This other screen takes some time to initialize, and I wanted to put a UIActivityIndicator on the UIBarButtonItem to show the tap has been registered, and the iPad is busy executing the action.
My approach was to add a UIActivityIndicator to the UIBarButtonItem after it was pressed, then call performSegueWithIdentifier:, and in the viewDidLoad method of the second view, put the initialization into a dispatch_sync() call. You can guess it does not work... why?
The IBAction on the first screen:
- (void)tappedEdit: (UIBarButtonItem *)editButton {
// put activity indicator somewhere
UIActivityIndicatorView *indicator;
indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
indicator.frame = CGRectMake (200, 5, 50, 50);
[self addSubview: indicator];
[indicator startAnimating];
// follow segue
[self performSegueWithIdentifier: SegueShowDesignMode sender: editButton];
}
The initialization on the second screen:
- (void)viewDidLoad {
[super viewDidLoad];
// put costly operations into another queue to free main queue for activity indicator
dispatch_sync (dispatch_get_global_queue (0, 0),
^{ // do initialization here
});
}
The effect of this is that the UIBarButtonItem stays tapped while the initialization is performed, then the UIActivityIndicator is visible for a quick moment, and lastly the segue animation is shown, displaying the second screen.
Any idea? Thanks a lot!
EDIT: Probably an addition to the problem is that the initialization does some UIKit stuff: When I tried to use a semaphore, the initialization dumps with a BAD ACCESS in some UITextView method. I guess this is because it runs on some 'get_global_queue' and not on the 'get_main_queue'.
EDIT AGAIN: Well, no. Using 'get_main_queue' results in a dead-lock, as announced in the Apple docs. So the question boils down to
"How can I do background UIView creation (lots of!) while still having a spinner running?"

I think one problem is that you are using dispatch_sync in your viewDidLoad method in your second screen. dispatch_sync will block your thread until the block with // do initialization here has completed. Try changing it to dispatch_async instead.
The other thing I'd consider doing if I were you is getting the UIActivityIndicator to appear as part of the second screen, not the first. That way, the second screen can also dismiss it after your view has finished the costly operations. Of course, this assumes that the view can actually display before those operations have completed.

if you are using a storyboard
ButtonBarItem.h
#import "CheckWifi.h"
interface LoadingViewController : UIViewController
-(IBAction)BarItemPressed:(id)sender;
ButtonBarItem.m:
-(IBAction)BarItemPressed:(id)sender {LoadingViewController *DIS = [self.storyboard instantiateViewControllerWithIdentifier:#"CheckWifi"];
DIS.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self presentModalViewController:DIS animated:YES];}
LoadingViewController.h:
#interface LoadingViewController : UIViewController {
IBOutlet UIActivityIndicatorView *spinner
}
-(IBAction)StopAnimating;
#end
LoadingViewController.m:
-(void)ViewDidLoad {
spinner.hidden = NO;
}
-(IBAction)StopAnimating {
spinner.hidden = YES;
}
If you're using an XIB File, then I cannot help you really, Sorry.

Related

Make animation work before changing view Objective-C

I have some questions. I'm a total novice to Objective-c and Xcode's development.
My problem is the following:
I have a button with an IBActions (cambia view) and an Outlet (gioca_outlet).
This button is connected to another view by the "Show", so when I press the next view appears.
But, before changing view I'd like to commit animations with the button, which has its own image.
I'd like to scale the button's image size (maintaining the same center) for 1/2 seconds, then show the second view.
I'd also like the button's size to be the initial one when I skip back to the main view.
So, I came up with this code:
- (IBAction)cambiaview:(id)sender {
[UIView animateWithDuration:2.0 delay:0.4 options:UIViewAnimationOptionTransitionCrossDissolve animations:{
[_gioca_outlet setCenter:CGPointMake([_gioca_outlet.center].x + 0.0001, [_gioca_outlet.center].y +0.0001)];
[_gioca_outlet setTransform:CGAffineTransformScale(_gioca_outlet.transform, 2.0, 2.0)];}
completion:^(BOOL finished) {
};
}
Back button, to return to the main view:
- (IBAction)back:(id)sender {
[self dismissViewControllerAnimated:YES completion:^{ //
}];
}
Thanks.
Open Interface Builder and delete all existing connections between two view controllers.
Then ctrl+drag from first view controller to the second and choose "Show" option.
step 1
After that click on the created connection (it's called segue), switch to Attributes Inspector and give it an ID step 2
In the completion block of your animation write [self performSegueWithIdentifier:#"showNextView"];
You have connected the button's action segue with the second view, so as soon as you clicked on the button, next view appears. Instead, you connect a segue from viewcontroller1 to viewcontroller2 and give an identifier to your segue.
Then on button's click event, add this code.
- (IBAction)cambiaview:(id)sender
[UIView animateWithDuration:0.8 animations:^{
//this will transform your button to grow a little bit bigger
_gioca_outlet.transform = CGAffineTransformMakeScale(1.5, 1.5);
}completion:^(BOOL finished) {
//push from viewcontroller1 to viewcontroller2 using you segue identifier
[self performSegueWithIdentifier:#"vc1tovc2segue" sender:self]
}];
This code will make your button a little bit bigger animatedly and then pushes to second view controller.
Now, if you want to play an audio, you can play it in second view controller's viewDidLoad method using AVFoundation framework.
To play the sound file do the following:
Add your sound file in your projects directory.
Import AVFoundation framework by writing,
#import <AVFoundation/AVFoundation.h>
Create a strong property of AVAudioPlayer in .h of your viewcontroller2.
#property (strong, nonatomic) AVAudioPlayer *player;
Then in viewcontroller2's viewDidLoad add this code to play the audio file,
- (void)viewDidLoad {
[super viewDidLoad];
//SampleAudio is your audio file name and mp3 is your extension.
NSString *soundFilePath = [[NSBundle mainBundle] pathForResource:#"SampleAudio" ofType:#"mp3"];
NSURL *soundFileURL = [NSURL fileURLWithPath:soundFilePath];
self.player = [[AVAudioPlayer alloc] initWithContentsOfURL:soundFileURL error:nil];
self.player.delegate = self;
[self.player play];
}
To make the button's size normal when you come back to your first view controller, add this code in your first view controller:
-(void)viewWillAppear:(BOOL)animated
{
_gioca_outlet.transform = CGAffineTransformIdentity;
}
Hope this helps.
If you face any problem understanding anything, feel free to leave a comment. :)

Memory keep increasing even though I set it to nil (screenshot enclosed)

In my ParentViewController I type this:
- (void)updateView:(NSInteger)_index article:(AObject *)aObject {
UIView *aUIView = [someUIViews objectAtIndex:_index];
// clear view
for (UIView *view in aUIView.subviews) {
[view removeFromSuperview];
}
// create view
ChildViewController *cvc = [[ChildViewController alloc] init:aObject context:managedObjectContext];
cvc.view.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
[self addChildViewController:cvc];
[aUIView addSubview:cvc.view];
cvc = nil;
}
In ChildViewController I type this:
.h file
#interface ChildViewController : UIViewController <UIWebViewDelegate> {
AObject *aObject;
UIWebView *webView;
}
.m file
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:(BOOL)animated];
webView.delegate = nil;
webView = nil;
aObject = nil;
}
Remark:
I have to use addChildViewController because I use UIWebViewDelegate in ChildViewController. If I do not use addChildViewController, I cannot retain the ViewController and functions of UIWebViewDelegate cannot be called.
I use ARC
Program Objective:
There is a scrolling view implementing by UIScrollView and everytime I scroll the screen to another page, function updateView will be called.
After I scroll to another page, the previous page will be disappeared so viewWillDisappear in ChildViewController will be called.
Screenshot:
http://postimage.org/image/mzmksm9d5/
I cannot post image directly because of low reputation here.
Based on the figure, ParentViewController is called at about the 4th second. Then the memory keep increasing rapidly without objects released. At about the 30th second, ParentViewController is gone away by clicking the back button on the navigation bar. Memory is dropped a bit but still somethings are remaining.
Goal:
What I wish to do is that the object of ChildViewController can be destroyed when viewWillDisappear is called and it is no longer used. So I expect the memory would always drop after it increased a bit. Otherwise, the memory will increase proportional to scrolling times and crash until memory is fully used.
Thanks in advance for any help you are able to provide.
Not sure if this will solve the problem, but try removing cvc.view explicitly from parent view.
Also, you are creating ChildViewController, but you are not releasing it. By calling init you increased its retain count, but you are not decreasing it anywhere. Like this (if you do not have some more code), your ChildViewController will not be released since retain count will never be 0 again.
ChildViewController *cvc = [[ChildViewController alloc] init:aObject context:managedObjectContext];
cvc.view.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
[self addChildViewController:cvc];
[aUIView addSubview:cvc.view];
cvc = nil;

performSelectorInBackground causes random crash when view is dismissing

I'm having some random crashes at this part of my code:
-(void) goBack {
[self performSelectorInBackground:#selector(addActivityIndicator) withObject:nil];
[self.navigationController popViewControllerAnimated:YES];
}
- (void)addActivityIndicator {
#autoreleasepool {
UIActivityIndicatorView *activityView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
UIBarButtonItem * barButton = [[UIBarButtonItem alloc] initWithCustomView:activityView];
[activityView startAnimating];
self.navigationItem.leftBarButtonItem = barButton;
}
}
When I want to exit the screen where these method exists, the other ViewController have to process some data. To inform the user that processing is occurring I add an activity indicator to the left button in my navigation bar.
The problem is that sometimes I get an exc_bad_access in addActivityIndicator method. The frequency is very random, sometimes the XCode shows the error at the end of #autoreleasepool, sometimes at the line self.navigationItem.leftBarButtonItem = barButton;
I imagine that sometimes my viewController is destroyed but the thread is still running and try to access the navigationItem of a object that don't exists anymore. But I'm not sure if that is the problem and I don't know how to fix it.
I'm using ARC in my project and this problem occurs in all iOS versions that I tested.
Please, anyone can explain me what is happening and how can I fix this?
Thanks.
You should never do UIKit stuff in the background.
By calling [self performSelectorInBackground:#selector(addActivityIndicator) withObject:nil]; you are updating the UI on a background thread. You should only ever update the UI on the main thread.
Edit
Based on your comment you are trying to have the UI update before the view pops. The way to do that would be:
[self addActivityIndicator]
[navigationController performSelector:#selector(popViewControllerAnimated:) withObject:[NSNumber numberWithBool:YES] afterDelay:0];
You could also look into dispatch_after

UIPageViewControllerSpineLocation Delegate Method Not Firing

Major head-scratcher all day on this one :-(
I have an instance of a UIPageViewController that does not appear to be firing the delegate method:
-(UIPageViewControllerSpineLocation)pageViewController:(UIPageViewController *)pageViewController
spineLocationForInterfaceOrientation:(UIInterfaceOrientation)orientation
I have tried various methods of displaying the UIPageViewController and have settled on a programatic approach (as opposed to a Storyboard one) that appears to be working correctly, with one exception... when rotating the iPad to landscape the spine does not appear mid-point as expected. I simply cannot find out why the delegate method does not get called.
Code Explanation (simplified for example)
Consider three classes as follows:
RootViewController - loaded when the app starts
PageViewController - loaded by RootViewController upon user initiation
PageContentViewController - loaded by PageViewController when pages are needed
Fairly self-explanatory. The RootViewController is loaded by the app upon launch. When the user taps an image within this view controller's view (think magazine cover opening a magazine) it launches the PageViewController as follows:
PageViewController *pvc = [[PageViewController alloc] initWithNibName:#"PageView"
bundle:[NSBundle mainBundle]];
pvc.view.frame = self.view.bounds;
[self.view addSubview:pvc.view];
In the actual app there is animation etc to make the transition all nice, but essentially the PageViewController's view is loaded and takes fullscreen.
PageViewController
This is the workhorse (only relevant methods shown). I have tried various examples from the infinite world of Google and written directly from the Apple docs...
#interface PageViewController : UIViewController <UIPageViewControllerDelegate, UIPageViewControllerDataSource>
#property (nonatomic, strong) UIPageViewController *pageViewController;
#property (nonatomic, strong) NSMutableArray *modelArray;
#end
#implementation TXCategoryController
-(void)viewDidLoad
{
[super viewDidLoad];
// Simple model for demo
self.modelArray = [NSMutableArray alloc] init];
for (int i=1; i<=20; i++)
[self.modelArray addObject:[NSString stringWithFormat:#"Page: %d", i]];
self.pageViewController = [[UIPageViewController alloc]
initWithTransitionStyle:UIPageViewControllerTransitionStylePageCurl
navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal options:nil];
self.pageViewController.delegate = self;
self.pageViewController.dataSource = self;
PageContentViewController *startupVC = [[PageContentViewController alloc] initWithNibName:#"PageContent" bundle:nil];
startupVC.pageLabel = [self.modelArray objectAtIndex:0];
[self.pageViewController setViewControllers:[NSArray arrayWithObject:startupVC]
direction:UIPageViewControllerNavigationDirectionForward
animated:NO
completion:nil];
[self addChildViewController:self.pageViewController];
[self.view addSubview:self.pageViewController.view];
[self.pageViewController didMoveToParentViewController:self];
self.pageViewController.view.frame = self.view.bounds;
self.view.gestureRecognizers = self.pageViewController.gestureRecognizers;
}
-(UIViewController *)pageViewController:(UIPageViewController *)pageViewController
viewControllerBeforeViewController:(UIViewController *)viewController
{
// Relevant code to add another view...
}
-(UIViewController *)pageViewController:(UIPageViewController *)pageViewController
viewControllerAfterViewController:(UIViewController *)viewController
{
// Relevant code to add another view...
}
-(UIPageViewControllerSpineLocation)pageViewController:(UIPageViewController *)pageViewController
spineLocationForInterfaceOrientation:(UIInterfaceOrientation)orientation
{
// Setting a break point in here - never gets called
if (UIInterfaceOrientationIsPortrait(orientation))
{
// Relevant code to create view...
return UIPageViewControllerSpineLocationMin;
}
// Relevant code to create 2 views for side-by-side display and
// set those views using self.pageViewController setViewControllers:
return UIPageViewControllerSpineLocationMid
}
#end
This all works perfectly well as I mentioned earlier. The PageViewController's view gets shown. I can swipe pages left and right in both portrait and landscape and the respective page number appears. However, I don't ever see two pages side-by-side in landscape view. Setting a breakpoint in the spineLocationForInterfaceOrientation delegate method never gets called.
This is such a head-scratcher I have burned out of ideas on how to debug/solve the problem. It almost behaves like the UIPageViewController isn't responding to the orientation changes of the device and therefore isn't firing off the delegate method. However, the view gets resized correctly (but that could be just the UIView autoresizing masks handling that change).
If I create a brand new project with just this code (and appropriate XIb's etc) it works perfectly fine. So something somewhere in my actual project is causing this. I have no idea where to continue looking.
As usual, any and all help would be very much appreciated.
Side Note
I wanted to add the tag 'uipageviewcontrollerspinelocation' but couldn't because it was too long and I didn't have enough reputation (1500 required). I think this is a devious ploy on Apple's part to avoid certain tags in Stackoverflow... ;-)
Finally found the problem. It was something of a red herring in its symptoms, but related just the same.
Putting a break point in the shouldAutorotateToInterfaceOrientation: method was a natural test to see if the UIViewController was even getting a rotation notification. It wasn't which led me to Apple's technical Q&A on the issue: http://developer.apple.com/library/ios/#qa/qa1688/_index.html
The most relevant point in there was:
The view controller's UIView property is embedded inside UIWindow but alongside an additional view controller.
Unfortunately, Apple, in its traditional documentation style, doesn't provide an answer, merely confirmation of the problem. But an answer on Stack Overflow yielded the next clue:
Animate change of view controllers without using navigation controller stack, subviews or modal controllers?
Although my RootViewController was loading the PageViewController, I was doing it as a subview to the main view. This meant I had two UIViewController's in which only the parent would respond to changes.
The solution to get the PageViewController to listen to the orientation changes (thus triggering the associated spine delegate method) was to remove addSubview: and instead present the view controller from RootViewController:
[self presentViewController:pac animated:YES completion:NULL];
Once that was done, the orientation changes were being picked up and the PageViewController was firing the delegate method for spine position. Only one minor detail to consider. If the view was launched in landscape, the view was still displaying portrait until rotated to portrait and back to landscape.
That was easily tweaked by editing viewDidLoad as follows:
PageContentViewController *page1 = [[PageContentViewController alloc] initWithNibName:#"PageContent" bundle:nil];
NSDictionary *pageViewOptions = nil;
NSMutableArray *pagesArray = [NSMutableArray array];
if (IS_IPAD && UIInterfaceOrientationIsLandscape(self.interfaceOrientation))
{
pageViewOptions = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:UIPageViewControllerSpineLocationMid]
forKey:UIPageViewControllerOptionSpineLocationKey];
PageContentViewController *page2 = [[PageContentViewController alloc] initWithNibName:#"PageContent" bundle:nil];
[pagesArray addObject:page1];
[pagesArray addObject:page2];
}
else
{
[pagesArray addObject:page1];
}
self.pageViewController = [[UIPageViewController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStylePageCurl
navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal
options:pageViewOptions];
self.pageViewController.delegate = self;
[self.pageViewController setViewControllers:pagesArray
direction:UIPageViewControllerNavigationDirectionForward
animated:NO
completion:NULL];
Job done and problem solved.

removeFromSuperview doesn't work properly

In class
#interface StartScene : UIView
I call an instance of
#interface HelpView : UIView {
GameOverMenu* gorm;
PlayScene* tView;
}
and use addSubview. I also got huge code here
-(void) removemyself {
[tView removeFromSuperview];
[gorm removeFromSuperview];
[self removeFromSuperview];
}
-(void)restartPlay {
[tView removeFromSuperview];
[self playSceneDidLoad];
}
-(void)gameOverDidLoad {
[tView removeFromSuperview];
gorm = [[GameOverMenu alloc]initWithFrame:CGRectMake(0, 0, 320, 520)];
gorm.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"backGround.jpg"]];
[gorm checkScore:Scores];
[self addSubview:gorm];
}
-(void)playSceneDidLoad {
[gorm removeFromSuperview];
tView = [[PlayScene alloc]initWithFrame:CGRectMake(0, 0, 320, 520)];
tView.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"backGround.jpg"]];
[self addSubview:tView];
[tView ooneFingerTwoTaps];
}
And two sub classes of HelpView:
#interface PlayScene : HelpView
#interface GameOverMenu : HelpView <UITextFieldDelegate>
In StartScene when I push on a button, an instance of HelpView is created and in init method playSceneDidLoad is called.
Inside the PlayScene there is restart button which calls restartPlay method. When game is lost gameOverDidLoad method is called.
And In both PlayScene and GameOverMenu there are quit button, which calls removemyself method, that are supposed to return player to the main menu.
At first glance it should work fine, but if I press restart button for several times and than try to press Quit, it occurs that the views were not removed from superview, one press on a quit button only now removes them one by one.
And we stop on the HelpView, it didn't remove itself (even if I try to call [super removeFromSuperview]; somewhere.
I need to remove views correctly in time and to get to the main menu (StartScene) when quit is pressed. I don't think that a lot of views covering each other is a good variant. What is the problem?
Well I occurs that the point is that if super class' method is called from the subclass and there is such a command [self removeFromSuperview]; or [(someOtherSubview) removeFromSuperview];, it is subclass that uses self or (someOtherSubview). If our subclass doesn't have the pointed subView, than the command would do nothing. And if there is [self removeFromSubview];, subclass would remove itself.
Actually I solved this problem by using buttons as subView of superclass.