I want to know if it is possible to load/present modally a UIViewController with a "free form" size like the image below
I want to load the red one allways on top (allways visible) and interact with the ViewController below (blue one) at the same time.
EDIT: what i want is to show and hide FROM APP DELEGATE a small view controller to controll the music in my app.
I was trying with a xib file with a custom UIViewController as the file owner (see image below). The xib is being showed right the way i want, but the UIViewController associated to it, id doesn't load and i don't know why...
So i thought to do it with a UIViewController free formed but im not making it and i don't know wich is the best way
EDIT2:
With the xib file way i did the next:
In AppDelegate i have a function showPlayer that is called with local notification to show the xib
-(void)showPlayer:(int)tag andObjetoCancion:(NSArray *)objetoCancion
{
ZocaloPlayerViewController *vc = [[ZocaloPlayerViewController alloc] initWithNibName:#"ZocaloPlayerView" bundle:nil];
[vc setObjectoCancion:[objetoCancion mutableCopy]];
if (globals.isPlaying) {
[vc setCambiar:YES];
}else{
[vc setCambiar:NO];
}
//here playerView is a UIView initiated in didFinishLaunchingWithOptions
if (playerView == nil) {
playerView = [[UIView alloc] initWithFrame:CGRectMake(0, self.window.frame.size.height, self.window.frame.size.width, altoPlayer)];
}
//keyWindow is UIWindow
//NSLog(#"Abro view %ld", (long)viewPlayer.tag);
keyWindow = [[[UIApplication sharedApplication] delegate] window];
[keyWindow addSubview:playerView];
[keyWindow bringSubviewToFront:playerView];
NSArray *subviewArray = [[NSBundle mainBundle] loadNibNamed:#"ZocaloPlayerView" owner:vc options:nil];
mainView = [subviewArray objectAtIndex:0];
mainView.translatesAutoresizingMaskIntoConstraints = NO; //This part hung me up
[playerView addSubview:mainView];
//needed to make smaller for iPhone 4 dev here, so >=200 instead of 748
[playerView addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:#"V:|-0-[mainView]-0-|"
options:NSLayoutFormatDirectionLeadingToTrailing
metrics:nil
views:NSDictionaryOfVariableBindings(mainView)]];
[playerView addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:#"H:|-0-[mainView]-0-|"
options:NSLayoutFormatDirectionLeadingToTrailing
metrics:nil
views:NSDictionaryOfVariableBindings(mainView)]];
[UIView animateWithDuration:0.3 animations:^{
playerView.center = CGPointMake(self.window.frame.size.width / 2, self.window.frame.size.height - mitadAltoPlayer);
[self.window layoutIfNeeded];
}];
}
ZocaloPlayerViewController *vc is being instantiated but the viewDidLoad function inside it is not being called.... That is my first problem...
With the UIViewController free formed way i don't know how to do it...
Ok, i solved my problem with this post Loaded nib but the view outlet was not set - new to InterfaceBuilder
(i was missing the UIView outlet)
and i changed my code in the showPlayer function to this:
-(void)mostrarPlayer:(int)tag andObjetoCancion:(NSArray *)objetoCancion
{
ZocaloPlayerViewController *vc = [[ZocaloPlayerViewController alloc] initWithNibName:#"ZocaloPlayerView" bundle:nil];
[vc setObjectoCancion:[objetoCancion mutableCopy]];
if (globals.isPlaying) {
[vc setCambiar:YES];
}else{
[vc setCambiar:NO];
}
if (playerView == nil) {
playerView = [[UIView alloc] initWithFrame:CGRectMake(0, self.window.frame.size.height, self.window.frame.size.width, altoPlayer)];
}
//NSLog(#"Abro view %ld", (long)viewPlayer.tag);
keyWindow = [[[UIApplication sharedApplication] delegate] window];
[keyWindow addSubview:playerView];
[keyWindow bringSubviewToFront:playerView];
[playerView addSubview:vc.view];
[UIView animateWithDuration:0.3 animations:^{
playerView.center = CGPointMake(self.window.frame.size.width / 2, self.window.frame.size.height - mitadAltoPlayer);
[self.window layoutIfNeeded];
}];
}
Related
I have a scene which is built as on the attached screen
Scene
Depending on the selection of a button from the top bar, I'm loading a specific view controller inside a container view. When selection is changed or the user is tapping the 'Back' button at controller then should be removed an intrinsic content of the container.
Code for adding into container view:
if (self.containerVC != nil) {
[self removeController];
}
UIStoryboard* storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
FirstViewController* firstVC = [storyboard instantiateViewControllerWithIdentifier:#"First"];
[self addChildViewController:firstVC];
[self.containerVieww addSubview:firstVC.view];
firstVC.view.frame = CGRectMake(0, 0, self.containerVieww.bounds.size.width, self.containerVieww.bounds.size.height);
[firstVC.view layoutSubviews];
[firstVC didMoveToParentViewController:self];
firstVC.modalPresentationStyle = UIModalPresentationCurrentContext;
[UIView animateWithDuration:0.5 animations:^{
self.logViewHeight.constant = self.contentView.frame.size.height * 0.9;
[firstVC.view layoutSubviews];
}];
firstVC.delegate = self;
self.containerVC = firstVC;
Code for removing from container view:
[self.containerVC dismissViewControllerAnimated:YES completion:nil];
[UIView animateWithDuration:0.5 animations:^{
self.logViewHeight.constant = 0;
[self.view layoutIfNeeded];
}];
[self willMoveToParentViewController:nil];
[self removeFromParentViewController];
[self.containerVC.view removeFromSuperview]; // Caused layout destoying !!!
self.containerVC = nil;
After calling 'removeFromSuperview' is disappeared a 'Blue View' in my layout.
How to remove from the container view without destroying a complex layout?
OK, I get the concept of adding a UIImageView as soon as the app launches, and animating it out to fake the splash animation. However, the status bar interferes.
I need a UIImageView on top of everything, including the status bar, and while it fades away, the app is shown with the status bar. Hence, settings the Status bar initially hidden, then animating it in is not a viable option.
What you require is a second UIWindow with a windowLevel of UIWindowLevelStatusBar or higher. You would create two UIWindow objects in your application delegate, one with your regular view hierarchy, the other with the image, and animate the second to fade out (or however else you need to animate). Both windows should be visible, with the splash window being on top.
This approach is complicated, as you might have problems with rotation, depending on your regular view hierarchy. We've done this in our software, and it works well.
EDIT:
Adapted Solution (window approach, very simple):
UIImageView* splashView = [[UIImageView alloc] initWithImage:[UIImage imageWithBaseName:#"Default"]];
[splashView sizeToFit];
UIViewController* tmpVC = [UIViewController new];
[tmpVC.view setFrame:splashView.bounds];
[tmpVC.view addSubview:splashView];
// just by instantiating a UIWindow, it is automatically added to the app.
UIWindow *keyWin = [UIApplication sharedApplication].keyWindow;
UIWindow *hudWindow = [[UIWindow alloc] initWithFrame:CGRectMake(0.0f, -20.0f, keyWin.frame.size.width, keyWin.frame.size.height)];
[hudWindow setBackgroundColor:[UIColor clearColor]];
[hudWindow setRootViewController:tmpVC];
[hudWindow setAlpha: 1.0];
[hudWindow setWindowLevel:UIWindowLevelStatusBar+1];
[hudWindow setHidden:NO];
_hudWin = hudWindow;
[UIView animateWithDuration:2.3f animations:^{
[_hudWin setAlpha:0.f];
} completion:^(BOOL finished) {
[_hudWin removeFromSuperview];
_hudWin = nil;
}];
Finally, credit goes to this guy.
A more simple approach would be to launch your application with the status bar hidden, have the view you would like to animate on the top of the view hierarchy, and after the animation is finished, display the status bar using [[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationSlide]
You can add a subview to the App's UIWindow. Set the Y coordinate of the UIImageView's frame to -20 pixels in order to handle the status bar.
Add the Default PNG image to your window and tag it:
static const NSInteger kCSSplashScreenTag = 420; // pick any number!
UIImageView *splashImageView;
// Careful, this wont work for iPad!
if ( [[UIScreen mainScreen] bounds].size.height > 480.0f ) // not best practice, but works for detecting iPhone5.
{
splashImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"Default-568h"]];
}
else
{
splashImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"Default"]];
}
splashImageView.frame = CGRectMake(0.0f, -20.0f, splashImageView.image.size.width, splashImageView.image.size.height);
splashImageView.tag = kCSSplashScreenTag;
[self.window addSubview:splashImageView];
[splashImageView release];
[self _fadeOutSplaceImageView];
Then fade it out
- (void)_fadeOutSplashImageView
{
UIView *splashview = [self.window viewWithTag:kCSSplashScreenTag];
if ( splashview != nil )
{
[UIView animateWithDuration:0.5
delay:0.0
options:0
animations:^{
splashview.alpha = 0.0f;
}
completion:^(BOOL finished) {
[splashview removeFromSuperview];
}];
}
}
I'm trying to have something similar to a UINavigationController so I can customize the animations. To start, I'm just using Apple stock animations. Here's my containerViewController:
- (void)loadView {
// Set up content view
CGRect frame = [[UIScreen mainScreen] bounds];
_containerView = [[UIView alloc] initWithFrame:frame];
_containerView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
self.view = _containerView;
}
- (id)initWithInitialViewController:(UIViewController *)vc {
self = [super init];
if (self) {
_currentViewController = vc;
[self addChildViewController:_currentViewController];
[self.view addSubview:_currentViewController.view];
[self didMoveToParentViewController:self];
_subViewControllers = [[NSMutableArray alloc] initWithObjects:_currentViewController, nil];
}
return self;
}
- (void)pushChildViewController:(UIViewController *)vc animation:(UIViewAnimationOptions)animation {
vc.view.frame = _containerView.frame;
[self addChildViewController:vc];
[self transitionFromViewController:_currentViewController toViewController:vc duration:0.3 options:animation animations:^{
}completion:^(BOOL finished) {
[self.view addSubview:vc.view];
[vc didMoveToParentViewController:self];
[self.subViewControllers addObject:vc];
}];
}
- (void)popChildViewController:(UIViewController *)vc WithAnimation:(UIViewAnimationOptions)animation {
// Check that there is a view controller to pop to
if ([self.subViewControllers count] <= 0) {
return;
}
NSInteger idx = [self.subViewControllers count] - 1;
UIViewController *toViewController = [_subViewControllers objectAtIndex:idx];
[vc willMoveToParentViewController:nil];
[self transitionFromViewController:vc toViewController:toViewController duration:0.3 options:animation animations:^{
}completion:^(BOOL finished) {
[vc.view removeFromSuperview];
[vc removeFromParentViewController];
[self didMoveToParentViewController:toViewController];
[self.subViewControllers removeObjectAtIndex:idx];
}];
}
I have this ContainerViewcontroller as my rootViewController of the window. I can add my initial viewController and push a view controller. When I try to pop though, I get
ContainerViewController[65240:c07] Unbalanced calls to begin/end appearance transitions for <SecondViewController: 0x8072130>.
I'm wondering what I am doing wrong. I figured my initialViewController is still underneath the secondViewController. Any thoughts? Thanks!
I don't know if this is what's causing your problem, but shouldn't this:
[self didMoveToParentViewController:toViewController];
be:
[toViewController didMoveToParentViewController:self];
Also, I'm not sure what you're doing with the subViewControllers array. It seems to be a duplication of the childViewControllers array that is already a property of a UIViewController.
One other thing I'm not sure is right. In your pop method your toViewController is the last controller in the _subViewControllers array. Don't you want it to be the second to last? Shouldn't the last be the one you're popping? You're popping vc, which is a controller you're passing in to the method, I don't understand that.
This is the way I've made a navigation like controller. In its containment behavior, it acts like a navigation controller, but without a navigation bar, and allows for different transition animations:
#implementation ViewController
-(id)initWithRootViewController:(UIViewController *) rootVC {
if (self = [super init]) {
[self addChildViewController:rootVC];
rootVC.view.frame = self.view.bounds;
[self.view addSubview:rootVC.view];
}
return self;
}
-(void)pushViewController:(UIViewController *) vc animation:(UIViewAnimationOptions)animation {
vc.view.frame = self.view.bounds;
[self addChildViewController:vc];
[self transitionFromViewController:self.childViewControllers[self.childViewControllers.count -2] toViewController:vc duration:1 options:animation animations:nil
completion:^(BOOL finished) {
[vc didMoveToParentViewController:self];
NSLog(#"%#",self.childViewControllers);
}];
}
-(void)popViewControllerAnimation:(UIViewAnimationOptions)animation {
[self transitionFromViewController:self.childViewControllers.lastObject toViewController:self.childViewControllers[self.childViewControllers.count -2] duration:1 options:animation animations:nil
completion:^(BOOL finished) {
[self.childViewControllers.lastObject removeFromParentViewController];
NSLog(#"%#",self.childViewControllers);
}];
}
-(void)popToRootControllerAnimation:(UIViewAnimationOptions)animation {
[self transitionFromViewController:self.childViewControllers.lastObject toViewController:self.childViewControllers[0] duration:1 options:animation animations:nil
completion:^(BOOL finished) {
for (int i = self.childViewControllers.count -1; i>0; i--) {
[self.childViewControllers[i] removeFromParentViewController];
}
NSLog(#"%#",self.childViewControllers);
}];
}
After Edit: I was able to duplicate the back button function with this controller by adding a navigation bar to all my controllers in IB (including in the one that is the custom container controller). I added a bar button to any controllers that will be pushed, and set their titles to nil (I got some glitches if I left the title as "item"). Deleting that title makes the button disappear (in IB) but you can still make connections to it in the scene list. I added an IBOutlet to it, and added this code to get the function I wanted:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
if (self.isMovingToParentViewController) {
self.backButton.title = [self.parentViewController.childViewControllers[self.parentViewController.childViewControllers.count -2] navigationItem].title;
}else{
self.backButton.title = [self.parentViewController.childViewControllers[self.parentViewController.childViewControllers.count -3] title];
}
}
I've shown two different ways that worked to access a title -- in IB you can set a title for the controller which I used in the else clause, or you can use the navigationItem title as I did in the if part of the clause. The "-3" in the else clause is necessary because at the time viewWillAppear is called, the controller that is being popped is still in the childViewControllers array.
addChildViewController should be called first
For adding / removing, you can refer to this great category and have no worry when to call it:
UIViewController + Container
- (void)containerAddChildViewController:(UIViewController *)childViewController {
[self addChildViewController:childViewController];
[self.view addSubview:childViewController.view];
[childViewController didMoveToParentViewController:self];
}
- (void)containerRemoveChildViewController:(UIViewController *)childViewController {
[childViewController willMoveToParentViewController:nil];
[childViewController.view removeFromSuperview];
[childViewController removeFromParentViewController];
}
In addition to rdelmar's answer you should not be calling addView/removeFromSuperview transitionFromViewController does this for you, from the documentation:
This method adds the second view controller’s view to the view
hierarchy and then performs the animations defined in your animations
block. After the animation completes, it removes the first view
controller’s view from the view hierarchy.
I am trying to build a custom container view controller and am trying to create my own animation. I'm starting with something simple of moving the tapped cell to the center of the screen before making it zoom in by scaling and showing the new view. I'm starting with this:
- (void)showDetailViewController:(UIView *)viewToAnimate {
UIImageView *anImageView = [[UIImageView alloc] initWithImage:[self screenShot:_homeViewController.view]];
CGRect windowBounds = [[UIScreen mainScreen] bounds];
_detailViewController.view.frame = windowBounds;
[self addChildViewController:_detailViewController];
// get a screenshot of the old view before pushing the new view controller
_detailViewController.view.alpha = 0.0;
NSLog(#"center: %#", NSStringFromCGPoint(viewToAnimate.center));
[self transitionFromViewController:_homeViewController toViewController:_detailViewController duration:0.5 options:0 animations:^{
CGPoint windowCenter = CGPointMake(CGRectGetMidX(windowBounds), CGRectGetMidY(windowBounds));
viewToAnimate.center = windowCenter;
viewToAnimate.layer.transform = CATransform3DMakeScale(2, 2, 2);
NSLog(#"center 2 : %#", NSStringFromCGPoint(viewToAnimate.center));
_detailViewController.view.alpha = 1.0;
}completion:^(BOOL finished) {
[anImageView removeFromSuperview];
}];
[_detailViewController didMoveToParentViewController:self];
}
What I don't understand is, if I comment out the _detailViewController.view.alpha = 0 and _detailViewController.view.alpha = 1, I don't get any animation. It just jumps to the new childViewController. But with those two lines, I do see an animation. Why is that? Thanks.
Edit:
This is how I set up a container controller:
In viewDidLoad of ContainerViewController:
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard_iPad" bundle:nil];
_homeViewController = [storyboard instantiateViewControllerWithIdentifier:#"HomeViewController"];
_homeViewController.homeViewControllerDelegate = self;
_detailViewController = [storyboard instantiateViewControllerWithIdentifier:#"DetailViewController"];
[self setSubViewControllers:#[ _homeViewController, _detailViewController]];
_selectedViewController.view.frame = [[UIScreen mainScreen] bounds];
[self addChildViewController:self.selectedViewController];
[self.view addSubview:_selectedViewController.view];
[self didMoveToParentViewController:self];
In the code you posted, you show yourself adding _detailViewController as a child view controller, but don't show the step of adding _homeViewController as a child view controller of self. At the point when the code you posted is executed, is _homeViewController a child of self (the view controller that is running the transition)? It needs to be.
I am using a UIView to show busy/processing clue to User and components in loading view are not responding to user touches. Let me elaborate my problem.
I have around 3 UIViewControllers and based on situation one View from VieControllers would be displayed. I use following code to change current view
UIView *currentView = self.view;
UIView *theWindow = [currentView superview];
//Some other code
[currentView removeFromSuperview];
[self.view setFrame: [tileCountViewController.view bounds]];
[theWindow addSubview:tileCountViewController.view];
[theWindow setFrame:[tileCountViewController.view bounds]];
[theWindow setFrame: [[UIScreen mainScreen] bounds]];
I then have a Loading view which generally used to show busy/processing kinda of UI to User. I created Loading view in following way from nib file
- (id)init {
UIDeviceOrientation orientation = [UIDevice currentDevice].orientation;
NSString *nibName = #"LoadingViewController";
if(UIInterfaceOrientationIsLandscape(orientation)) {
nibName = #"LoadingView-landscape";
}
else {
nibName = #"LoadingView";
}
UINib *nib = [UINib nibWithNibName:nibName bundle:nil];
UIView *portraitView = [[nib instantiateWithOwner:self options:nil] objectAtIndex:0];
self = [super initWithFrame:portraitView.frame];
[self addSubview:portraitView];
[self bringSubviewToFront:portraitView];
return self;
}
In app delegate, I initialize loading view and add it to Main window
if(!loadingViewIndicator){
loadingViewIndicator = [[LoadingView alloc]init];
}
[[[UIApplication sharedApplication]keyWindow]addSubview:loadingViewIndicator];
loadingViewIndicator.hidden = YES;
Whenever I need to show loading/busy view, I simply load it like this
[loadingViewIndicator performSelectorOnMainThread:#selector(startLoadingViewIndicator:) withObject:startMsg waitUntilDone:NO];
- (void)startLoadingViewIndicator:(NSString *)startMsg{
self.hidden = NO;
[self.superview bringSubviewToFront:self];
[self.activityIndicator startAnimating];
[self.loadingMessage setText:startMsg];
self.loadingProgressView.progress = 0.0f;
[self refreshPosition];
}
Everything works fine but components in Loading view are not responding to touches. Any clue what is going wrong?
I just realized that problem is due to threading issue. I show this subview when User starts downloading any file. My earlier code was to directly start download
urlconnection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately: YES];
I am now calling this method from another method using background thread as below
[self performSelectorInBackground:#selector(startDownload) withObject:nil];
And then I am adding runloop in download like
urlconnection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately: YES];
CFRunLoopRun();
This works fine and my subview is responsive. It really took lots of time. The problem is there is clear code example to help to run download in background thread and at the same time make UI responsive.