I have rather complicated user interface for my application that supports both Portrait & Landscape orientations (excluding PortraitUpsideDown). There are two View Controllers and thus two Views. The first one represents Portrait UI and the other Landscape. Both controllers are related with the same class. I have created two Outlets for each kind of orientation and several Outlet Collections for other UI objects. The code that I use for animating the rotation is below.
This is how I declare those properties.
#property (strong, nonatomic) IBOutlet UIView *landscape;
#property (strong, nonatomic) IBOutlet UIView *portrait;
And the process of animation.
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
if(toInterfaceOrientation == UIInterfaceOrientationPortrait) {
self.view = self.portrait;
self.view.transform = CGAffineTransformIdentity;
self.view.transform = CGAffineTransformMakeRotation(degreesToRadius(0));
self.view.bounds = CGRectMake(0.0, 0.0, 320.0, 568.0);
} else if(toInterfaceOrientation == UIInterfaceOrientationLandscapeLeft) {
self.view = self.landscape;
self.view.transform = CGAffineTransformIdentity;
self.view.transform = CGAffineTransformMakeRotation(degreesToRadius(-90));
self.view.bounds = CGRectMake(0.0, 0.0, 568.0, 300.0);
} else if(toInterfaceOrientation == UIInterfaceOrientationLandscapeRight) {
self.view = self.landscape;
self.view.transform = CGAffineTransformIdentity;
self.view.transform = CGAffineTransformMakeRotation(degreesToRadius(90));
self.view.bounds = CGRectMake(0.0, 0.0, 568.0, 300.0);
}
}
The problem is being that the second (landscape) view doesn't exist neither at the moment of rotation nor at the moment of View's loading. Code samples that I use as an example don't work for me due to this issue. As well as I understand the thing, the second View isn't automatically loaded and I should do something about that in my ViewController...
Please help me solve that problem! Thank you in advance!
P.S. Everything works fine with using a simple .XIB file for managing UI. I just simply drag two Views and it's okay because they are both handled by the same controller. So what do I do for my Storyboard?
Related
Since I am very new to ios programming I have more of a general-design question.
I have a ViewController which contains a GraphView (UIScrollView + UIView) which works fine. When I am rotating to landscape I want the GraphView to resize its height to the display height (so it fills the whole screen) but only 300pts when in portrait.
What I did so far is implementing viewWillLayoutSubviews in the ViewController and resetting the constraints:
- (void)viewWillLayoutSubviews{
_graphViewHeightConstraint.constant = ([[UIDevice currentDevice] orientation] == UIDeviceOrientationPortrait) ? 300:[[UIScreen mainScreen] bounds].size.height-self.navigationController.navigationBar.frame.size.height - 2*_distanceToTopView.constant;
}
and in GraphView.m:
- (void)layoutSubviews{
kGraphHeight = self.frame.size.height;
[self setNeedsDisplay];
}
(because I need the variable kGraphHeight in the code to draw the Graph). This does not seem like a very elegant solution so I wanted to ask what the better way would be? Many thanks for your inputs :)
In GraphView.m
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
kViewWidth = <GET_SCREEN_WIDTH_HERE>;
kViewHeight = <GET_SCREEN_HEIGHT_HERE>;
[self updateViewDimensions];
}
and updateViewDimensions method will set the frame of UIScrollView and UIView
- (void)updateViewDimensions
{
scrollView.frame = self.view.frame;
yourView.frame = CGRectMake(kViewXStartsFrom, kViewYStartsFrom, kViewWidth, kViewHeight);
}
after rotating view to Landscape viewDidLayoutSubviews will be called.
It's working for me.
I am trying to make a custom expand viewcontroller animation in iOS.
It's a iPad application and has a big square button of size 160*160 in the home screen. I have connected a modal segue to the next viewcontroller scene. What I am trying to achieve is when I tap on the UIButton the destination viewcontroller should expand from the button and shall be animated to full screen with animation completion.
I have subclassed NSObject and created ExpandAnimationController, implemented transitionDuration and animateTransition methods. But the I am stuck at a point to determine the initial frame for the destination viewcontroller's view.
I know for one button I can set frame statically but where in a scenario appears that there are multiple buttons and each button has to a modal segue to a different view controller than how can I pass the buttons frame to the animation controller to set the initial view frame.
I am doing this only for learning purpose after trying raywanderlich's custom view controller transition tutorial. So the problem now is passing a frame object from "Source controller to Animation controller". I've already searched, internet is not much populated with custom transition issues. Any help will be appreciated.
Here is the code I've tried, in ExpandAnimationController,
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
// 1. Fetch all the required objects.
UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIView *containerView = [transitionContext containerView];
CGRect finalFrame = toVC.view.frame;
// 2. Set initial frames.
// This is where I am stuck.
CGRect initialFrame = CGRectZero;
toVC.view.frame = initialFrame;
[containerView addSubview:toVC.view];
// 3. Do the animation.
NSTimeInterval duration = [self transitionDuration:transitionContext];
[UIView animateWithDuration:duration animations:^{
fromVC.view.alpha = 0.5;
toVC.view.alpha = 0.5;
toVC.view.frame = finalFrame;
} completion:^(BOOL finished) {
fromVC.view.alpha = 1.0;
toVC.view.alpha = 1.0;
toVC.view.frame = finalFrame;
[transitionContext completeTransition:YES];
}];
}
In the source controller I've adopted UIViewControllerTransitioningDelegate, created a ExpandAnimationController instance and used like below,
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source {
return _expandAnimationController;
}
But after - prepareForSegue is called I want to pass the sender button frame to the transition animation controller.
Thanks in advance, Happy coding !!
There are many approaches on how you could do this, like an iVar to your transition or transitioning delegate that you could pass in the button rect, but I'm going to suggest a simpler approach (although with a caveat):
First assign a unique Tag to your UIButton either by code, or in Interface Builder.
In this example I'm using the 111 tag:
In your - (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext you can get a reference to that button from your ViewController:
CGRect myButtonRect = CGRectZero;
UIButton *myButton = (UIButton*)[fromVC.view viewWithTag:111];
if (myButton) {
myButtonRect = myButton.frame;
NSLog(#"button Frame:%#",NSStringFromCGRect(myButtonRect));
}
What's the caveat?
If you are using the same animation to transition back, the From and To View controllers are going to be reversed.
Also if you use the same Transition on another View Controller and that View Controller has the same tag number for another object, you will get that object and not your Button.
So if you use this approach make sure to use a globally Unique Tag for your entire App, like 32565 (random number here that is big enough so you won't re-use it again)
Now if you have multiple buttons, you cannot use the above approach, so use an iVar to your custom transition:
#interface MyCustomTransition : NSObject <UIViewControllerAnimatedTransitioning>
#property (nonatomic, assign) CGRect initialFrame;
#end
Then in the view controller you are using your custom transition, just pass in the frame:
MyCustomTransition *myTransition = [MyCustomTransition new];
myTransition.initialFrame = myButton.frame;
And finally in your - (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext get the iVar value from self.initialFrame
You can convert CGRect to NSValue and put it into sender.
// Send it like this
[self performSegueWithIdentifier:#"someIdentifier" sender:[NSValue valueWithCGRect:yourFrame]];
Also you can retrieve it like this
// Retrieve it
CGRect frame = [sender CGRectValue];
I have got my view designed like that: under the bottom edge of the screen a subview is located. Once the app launched and viewDidLayoutSubviews called i animate the view sliding out from the bottom. My problem is - each time the status bar changes (receiving the call for example) the bottom view is been slide-out again.
Here is my code:
[UIView animateWithDuration:0.7 animations:^ {
self.bottomView.frame = CGRectMake(0.0,
self.view.frame.size.height - heightOfBot,
self.bottomView.frame.size.width,
self.bottomView.frame.size.height);
}];
Use a property:
#property (nonatomic) BOOL bottomViewVisible;
And a method:
- (void)showBottomView {
if ( !self.bottomViewVisible ) {
self.bottomViewVisible = YES;
[UIView animateWithDuration:0.7 animations:^{
self.bottomView.frame = CGRectMake(0.0,
CGRectGetHeight(self.view.bounds) - heightOfBot,
CGRectGetWidth(self.bottomView.frame),
CGRectGetHeight(self.bottomView.frame));
}];
}
}
(I changed your CGRect direct member access to use the functions from CGGeometry.h as per the docs.)
I created a UICollectionView in the interface builder. I referenced it with
#property (strong, nonatomic) IBOutlet UICollectionView *contactList;
in my .h file and #synthesize contactList; in my .m file.
I tried to implement a slightly different layout in portrait and landscape using the following code:
- (void) adjustViewsForOrientation:(UIInterfaceOrientation) orientation {
if (orientation == UIInterfaceOrientationPortrait || orientation == UIInterfaceOrientationPortraitUpsideDown) {
UICollectionViewFlowLayout *flow = (UICollectionViewFlowLayout*)contactList.collectionViewLayout;
flow.sectionInset = UIEdgeInsetsMake(48, 80, 48, 0);
flow.minimumLineSpacing = 80;
} else if (orientation == UIInterfaceOrientationLandscapeLeft || orientation == UIInterfaceOrientationLandscapeRight) {
UICollectionViewFlowLayout *flow = (UICollectionViewFlowLayout*)contactList.collectionViewLayout;
flow.sectionInset = UIEdgeInsetsMake(48, 64, 48, 0);
flow.minimumLineSpacing = 64;
}
}
This works prefectly, but I also want to change the size (in landscape I have a 2*3 grid per page and in portrait it's a 3*2 grid). I tried setting the size like this:
CGRect frame = [contactList frame];
frame.size.width = 960;
[contactList setFrame:frame];
But it won't update. There is no error, but the view just isn't updated. It just stays at the same size as before. What do I have to do to update the view?
In my app I always using this:
[self.contactList performUpdates:^{
CGRect frame = [contactList frame];
frame.size.width = 960;
[contactList setFrame:frame];
[self.contactList.collectionViewLayout invalidateLayout];
} completion:nil];
This should set your frame and update your layout.
When are you modifying the frame of the contactList? At VC initialization time, outlets are nil. Your view controller gets a ViewDidLoad call after the outlets are connected.
I suspect that contactList is still nil when your resizing code is being executed. Move that code's execution into a ViewDidLoad method, and you'll be able to access the real deal.
You can easily test this theory by adding the following to where you resize the frame:
NSLog(#"contactList is: %#",contactList.description);
i think you try to do
[self.yourcontrollerview reloaddata]
when you modify the frame..
I am now testing an app for Ipad. Basically I am using template for master detail application and have another portraitViewController. Now when application starts in portrait mode I want it to display only portraitViewController and when device is rotated e.g landscape mode I want to display only master-detailViewController. What is the best way to do this.
I was testing sample code for single view application but master-detail view refuses to hide:
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)
interfaceOrientation duration:(NSTimeInterval)duration {
if (interfaceOrientation == UIInterfaceOrientationPortrait) {
self.view = self.portrait;
self.view.transform = CGAffineTransformIdentity;
self.view.transform =
CGAffineTransformMakeRotation(degreesToRadians(0));
self.view.bounds = CGRectMake(0.0, 0.0, 320.0, 460.0);
}
else if (interfaceOrientation == UIInterfaceOrientationLandscapeLeft) {
self.view = self.landscape;
self.view.transform = CGAffineTransformIdentity;
self.view.transform =
CGAffineTransformMakeRotation(degreesToRadians(−90));
self.view.bounds = CGRectMake(0.0, 0.0, 480.0, 300.0);
}
else if (interfaceOrientation ==
UIInterfaceOrientationLandscapeRight) {
self.view = self.landscape;
self.view.transform = CGAffineTransformIdentity;
self.view.transform =
CGAffineTransformMakeRotation(degreesToRadians(90));
self.view.bounds = CGRectMake(0.0, 0.0, 480.0, 300.0);
} }
The view controller should not be changing its view in real time like this. You're not seeing anything happening because the view controller's old view is still in the interface; you're not removing it from the interface. Nor should you do so. The way to make that happen is to swap out the view controller; use a different view controller and a different view.
You started with the master-detail view controller, so the app's root view controller is the UISplitViewController. It's view (the split view), it appears, is the view you want to remove. So you will have to replace the UISplitViewController as the window's rootViewController.
But that's a huge pain in the butt. I think you might be happier just putting a modal (presented) view controller in front of everything when in portrait orientation.
This is a downloadable example project that presents a view controller in response to device rotation:
https://github.com/mattneub/Programming-iOS-Book-Examples/tree/master/ch19p609rotationForcingModalView