Make animation work before changing view Objective-C - 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. :)

Related

Objective-C: How to switch the NSView by button click

I have different xib files with NSViewController attached to them. (Screenshot below)
One of xib file called StartMenuViewController which has a button. I want to click that button and change the view to DetectingUSBViewController.(Screenshot below)
The IBAction of that button is in StartMenuViewController.m file.
And I use AppController.m to control my main xib view.(NSWindow + NSView) (Screenshot below)
When the application runs, I try to initialize the StartMenuViewController fist by doing the following thing in my AppController.m file.
-(void)awakeFromNib{
[self initialize];
}
-(void) initialize
{
#autoreleasepool {
//mainViewController is a NSViewController and _mainView is a NSView which connect with Custom View in main xib
self.mainViewController = [[[StartMenuViewController alloc]initWithNibName:StartMenuView bundle:nil]autorelease];
[_mainView addSubview:[_mainViewController view]];
}
}
It works fine and it will show the StartMenuViewController.xib on the window at first, but I do not know how to change the view after clicking the button(FIND USB DRIVE). I want the current view changes to DetectingUSBViewController.xib.
Simplest way possible, assuming you have tied your USB button properly in, do the following :
- (IBAction)usbButton:(UIButton *)sender {
DetectingUSBViewController *second = [[DetectingUSBViewController alloc] initWithNibName:#"DetectingUSBView" bundle:nil];
[self presentViewController:second animated:YES completion:nil];
}
load the DetectingUSBViewController in startMenuViewController as DetectingUSBViewController* v1 = [[ViewCont1 alloc] initWithNibName:#"ViewCont1" bundle:nil]; now add or replace the view as [v1 view] in view where you want to add/replace.
You need to hook up your button to send an IBAction
You need a 'View for DetectingUSBViewController.xib'
=> one way (iOS like) is to use a ViewController. Subclass NSViewController and then alloc init a DetectingUSBViewController
Add the view. Don't present the VC (as there is no such thing in OSX)
//button click action
- (IBAction)usbButton:(UIButton *)sender {
//! Retain the VC
Self.detectingUSBViewController = [[DetectingUSBViewController alloc] initWithNibName:#"DetectingUSBView" bundle:nil];
//add the view
[_mainView addSubview:[_detectingUSBViewController view]];
}

Show UIActivityIndicator during segue navigation, after UIBarButtonItem press

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.

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.

Adding splashscreen to iphone app in AppDelegate

I'm running xcode-4.2 and the project is based for ios5 using storyboards.
I've created a single view application , using the template provided by Apple.
In the storyboard I the removed the viewcontroller created for me and added a UITabBarController.
Next I added a new class MyTabBarController which is a subclass of UITabBarController.
Now I want to show a splashscreen before the TabBar appears. So I can do some loading and calculation in the background.
I thought AppDelegate.m would be a good place for this. Since that's the place where my rootview get's loaded not ? Or should a show the splashscreen from the rootviewcontroller which is MyTabBarController in my case ?
So I created a xib file. I'm surprised you can add .xib files to ios5 storyboard projects. The xib file is called SplashView.xib it has a single view with an image on it.
Code in AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
_splashScreen = [[UIViewController alloc] initWithNibName:#"SplashView" bundle:nil];
//_splashScreen is defined as:#property (strong, nonatomic) UIViewController *splashScreen;
[_window.rootViewController presentModalViewController:_splashScreen animated:NO];
[self performSelector:#selector(hideSplash) withObject:nil afterDelay:2];
return YES;
}
The problem is nothing happens. Even if I change the value from 2 to 200. The application starts up as if there is no splashscreen.
As you might have noticed I'm still struggling with the design of objective-c and iphone application. I hope a decent answer to my question will bring some clarity to the subject.
Thanks in advance!
Splash screens are built into iOS apps. All you need to do is create a file called Default.png and Default#2x.png (for retina displays) and it will work as a splash screen for when the app launches.
You can also set what these images will be in your apps info.plist.
I've dealt with a few clients who wanted to use an animated splash.
Though I'm totally against this, following Apple's HIG,
those clients just don't understand...
Anyway, since - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions; has to return boolean,
it's very important not to halt it for anything.
Also, since launch time is measured by iOS, if it's taking too long, the app will be terminated by iOS!
For this reason, I often use - (void)applicationDidBecomeActive:(UIApplication *)application;
with some kind of flag to indicate if it happened at launch or at returning from background mode.
Or, you should use a NSTimer or - (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay;
so, didFinishLaunchingWithOptions can return without being blocked for processing your animated splash.
This delayed performSelector should be implemented not only for hiding action (like the way you intended it), but also for starting the animation.
If you are using storyboard, you can just add the splash UIImageView to your window.rootViewController.view like this:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
UIImage *splashImage = [UIImage autoAdjustImageNamed:#"Default.png"];
UIImageView *splashImageView = [[UIImageView alloc] initWithImage:splashImage];
[self.window.rootViewController.view addSubview:splashImageView];
[self.window.rootViewController.view bringSubviewToFront:splashImageView];
[UIView animateWithDuration:1.5f
delay:2.0f
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
splashImageView.alpha = .0f;
CGFloat x = -60.0f;
CGFloat y = -120.0f;
splashImageView.frame = CGRectMake(x,
y,
splashImageView.frame.size.width-2*x,
splashImageView.frame.size.height-2*y);
} completion:^(BOOL finished){
if (finished) {
[splashImageView removeFromSuperview];
}
}];
return YES;
}
I think the reason why directly just add the UIImageView to window is because iOS will bring the rootViewController.view to front when the default splash will hide. And this will overlap the animation. This means the animation does happen but it's behind the rootViewController.
I just add an identical image to the launch image to my first view controller and then fade it (or whatever animation you require) - this avoids pausing the app load in the AppDelegate.
You need to ensure that the image has the same size and origin as your launch image e.g. to set the image to display on my first view controller which is a tableViewController:
UIImageView *imageView = [[UIImageView alloc] initWithFrame:self.tableView.bounds];
imageView.image = [UIImage imageNamed:#"[imagename]"];
[self.tableView addSubview:imageView];
[self.tableView bringSubviewToFront:imageView];
// Fade the image
[self fadeView:imageView];
-(void)fadeView:(UIView*)viewToFade
{
[UIView animateWithDuration:FADE_DURATION
animations:^ {
viewToFade.alpha = 0.0;
}
];
}

xcode adding buttons to navigation bar

I am making a simple app to display drink details, and now I am trying to add a view that allows the user to input their own drink. I already created a view to display the details, and now I am just passing the view into another controller to make the add drink view. Problem is, when I try to add a "cancel" and "save" button, it doesn't appear, although the code complies without any errors. I have attached code as reference.
This is the code that makes the new view, when the add button is pressed. (I made an add button that works, and it pulls up the nav bar)
- (IBAction)addButtonPressed:(id)sender {
AddDrinkViewController *addViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"DetailSecond"];
UINavigationController *addNavController = [[UINavigationController alloc] initWithRootViewController:addViewController];
[self presentModalViewController:addNavController animated:YES];
NSLog(#"Add button pressed!");
This is the code from the addviewcontroller implementation file:
- (void)viewDidLoad
{
[super viewDidLoad];
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:#selector(cancel:)];
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSave target:self action:#selector(save:)];
}
- (IBAction)save:(id)sender {
NSLog(#"Save Pressed");
[self dismissModalViewControllerAnimated:YES];
}
- (IBAction)cancel:(id)sender{
NSLog(#"Cancel Pressed");
[self dismissModalViewControllerAnimated:YES];
}
I have imported the header from the addview into the root controller, so I don't think that is the problem, do any of you guys see anything that's wrong?
Just change the line
[self presentModalViewController:addNavController animated:YES];
to
[self presentViewController:navigationController animated:YES completion:nil];
and see the magic. I also tested the code
My advice to you is to create a template for the view before you run through any code in the XIB file of your app. Rather than trying to set each button after allocating a brand new view, setting a new one in the XIB before-hand allows you to link each element with the app and make sure it looks just right before you debug.
Simply go into your "[Your-App-Name]viewController.xib" and drag a view from the objects library to the pane on the left. From here add each of your elements and position them where you want on the view. Now in the "[Your-App-Name]viewController.h" file, add IBOutlets for each element that you need to change, and add IBActions for each of the buttons. Also create an IBOutlet for the new view.
IBOutlet UIView* addDrinkView;
Back in the XIB file, use files owner to link each outlet to each element and each method to each button. Make sure you link the IBOutlet
Now in your "[Your-App-Name]viewController.m" file, you can define each button method and all you need to do to access the new view and dismiss it are the following:
-(IBAction)openAddView
{
[self setView:addDrinkView];
}
-(IBAction)saveButtonPressed
{
[self setView:view];
//save code goes here
}
-(IBAction)cancelButtonPressed
{
[self setView:view];
//cancel code goes here
}
This should be much easier than trying to position everything in code.
Hope this helps!