I am building an app that displays an image in landscape and portrait modes. Rotating works perfectly. The image is also perfectly positioned in landscape mode. However it keeps its landscape coordinates in portrait, which misplace it as a result. Please find my code below. Could you let me know what I'm missing? Is there also a way to achieve this strictly from a Xib file?
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
[[self navigationController] setNavigationBarHidden:YES animated:NO];
UIImage *startImage = [UIImage imageNamed:#"title.png"];
UIImageView *startImageView = [[UIImageView alloc] initWithImage:startImage];
if (curOrientation == UIInterfaceOrientationPortrait || curOrientation == UIInterfaceOrientationPortraitUpsideDown) {
[startImageView setFrame:CGRectMake(-128, 0, 1024, 1024)];
}else{
[startImageView setFrame:CGRectMake(0, -128, 1024, 1024)];
}
[self.view addSubview:startImageView];
}
Currently you are only calling this code when the view is first loaded. You actually need to call it
whenever the view appears onscreen (in case the device was rotated while it was offscreen)
whenever the device is rotated
but you should keep the view creation code in viewDidLoad, as you only want to create it once.
Make a property to keep a pointer to the view so that you can refer to it from all of these places in your codeā¦
#property (nonatomic, weak) UIImageView* startImageView;
Create it in viewDidLoad (but don't worry then about the geometry, as you can do this in viewWillAppear):
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
[[self navigationController] setNavigationBarHidden:YES animated:NO];
UIImage *startImage = [UIImage imageNamed:#"title.png"];
UIImageView *startImageView = [[UIImageView alloc] initWithImage:startImage];
self.startImageView = startImageView;
[self.view addSubview:startImageView];
}
Make a generic orientation method:
- (void) orientStartImageView
{
UIInterfaceOrientation curOrientation = [UIApplication sharedApplication].statusBarOrientation;
if (curOrientation == UIInterfaceOrientationPortrait || curOrientation == UIInterfaceOrientationPortraitUpsideDown) {
[self.startImageView setFrame:CGRectMake(-128, 0, 1200, 1200)];
}else{
[self.startImageView setFrame:CGRectMake(0, -128, 1200, 1200)];
}
}
Call it from viewWillAppear (triggered every time the view comes onscreen):
- (void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self orientStartImageView];
}
Call it from viewWillLayoutSubviews (triggered every time the view IS onscreen and the device rotates):
- (void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
[self orientStartImageView];
}
By the way, I am not sure your frames are correct - in portrait you are shifting the left edge offscreen, in landscape you are shifting the top edge offscreen. Is that what you want? It may well be that you can achieve what you want in Interface Builder, but it is not clear from your code what that is - maybe you could post a picture. Also check that you have Autolayout disabled (checkbox in Interface Builder's file inspector) to simplify issues.
update
You may be able to do this from the Xib with no code: centre the imageView in it's superView, set it's size to your final size (eg 1200x1200), disable Autolayout, deselect all springs and struts, set your View Mode appropriately (eg center or scaleToFill)
Related
I want the image in my UIImageView to us a different image file that's cropped to correctly fill the landscape mode upon the orientation changing from portrait to landscape. All of the following code is defined in a class that extends UIViewController.
The following function is definitely being called when the phone rotates because my printf function is printing to the output.
- (void) traitCollectionDidChange: (UITraitCollection *) previousTraitCollection {
[super traitCollectionDidChange: previousTraitCollection];
if ((self.traitCollection.verticalSizeClass != previousTraitCollection.verticalSizeClass)
|| (self.traitCollection.horizontalSizeClass != previousTraitCollection.horizontalSizeClass)) {
printf("Orientation Change!\n");
UIImage * newImage = [UIImage imageNamed: #"landscape-image"];
[self.imageView setImage:newImage];
}
}
I defined the property imageView in my .h file as follows:
#property (nonatomic, nonnull, readonly) UIImageView * imageView;
And I initialize the imageView as follows:
UIImage * image = [UIImage imageNamed:#"portrait-image"];
UIImageView * _imageView = [[UIImageView alloc] initWithImage:image];
_imageView.contentMode = UIViewContentModeScaleAspectFill;
_imageView.translatesAutoresizingMaskIntoConstraints = NO;
[pageView addSubview:_imageView];
This does not work for me though. When I change the orientation, the imageView image stays the same and zooms in like it normally does. Since the traitCollectionDidChange function is being called when the phone rotates, I assume the issue must be with how I'm changing the image. I'm relatively new to iOS development so I could just be missing something important for updating UIImageViews. Any help is appreciated.
When you create the imageView using UIImageView * _imageView = [[UIImageView alloc] initWithImage:image];, you are shadowing the automatically synthesised variable _imageView, with a new variable of the same name.
The newly created UIImageView instance is therefore not assigned to the imageView property. As a result, when the device is rotated and the second method is run, self.imageView is nil, and you call to [self.imageView setImage:newImage] does nothing.
To solve it, all you need to do replace UIImageView * _imageView = [[UIImageView alloc] initWithImage:image]; with _imageView = [[UIImageView alloc] initWithImage:image];
More on the messiness of automatically synthesised properties in Objective-C in this answer When should I use #synthesize explicitly?
EDIT --
In addition, the - (void) traitCollectionDidChange: (UITraitCollection *) previousTraitCollection may be called at any time, and initially, so you want to detect the orientation in that method, and not just set the image to your landscape image.
(and on this - ou may want to re-evaluate your requirements regarding 'landscape' vs 'portrait' image because iOS apps can run in a variety of environment, iPad / iPhone / compact.. it is complicated. https://developer.apple.com/documentation/uikit/uitraitcollection)
To complete the answer, this should work on iPhone
- (void) traitCollectionDidChange: (UITraitCollection *) previousTraitCollection {
[super traitCollectionDidChange: previousTraitCollection];
if ((self.traitCollection.verticalSizeClass != previousTraitCollection.verticalSizeClass)
|| (self.traitCollection.horizontalSizeClass != previousTraitCollection.horizontalSizeClass)) {
if (self.traitCollection.verticalSizeClass == UIUserInterfaceSizeClassCompact) {
[self.imageView setImage:[UIImage imageNamed: #"landscape-image"]];
} else if (self.traitCollection.verticalSizeClass == UIUserInterfaceSizeClassRegular
&& self.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassCompact) {
[self.imageView setImage:[UIImage imageNamed: #"portrait-image"]];
}
}
}
I've recently added support to iOS 11 on my app and this started happening. Basically, whenever a ViewController is added to the navigation stack the tab bar glitches out during the animation.
It only happens in iPhone X, and this is just a regular TabBarController. What's causing it?
Additional answer
A radar is open about this problem here.
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
// Disable tabBar shifts upward whenever a ViewController is pushed on iPhone X rdar://35098813
BOOL isIPhoneX = ...
if (isIPhoneX && UIInterfaceOrientationIsPortrait([UIApplication sharedApplication].statusBarOrientation)) {
[self.tabBar setFrame:CGRectMake(0, CGRectGetHeight(self.view.frame) - CGRectGetHeight(self.tabBar.frame), CGRectGetWidth(self.view.frame), CGRectGetHeight(self.tabBar.frame))];
}
}
Original answer
I think this is a bug of iOS 11.
You can remove that weird effect to put down this code to your subclass of UITabBarController.
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
BOOL isIPhoneX = ...
if (isIPhoneX && UIInterfaceOrientationIsPortrait([UIApplication sharedApplication].statusBarOrientation)) {
[self.tabBar setFrame:CGRectMake(0, self.view.frame.size.height - 83, 375, 83)];
}
}
The solution is weird, too. :)
In my app i trying create a Interface builder supports both Landscape and portrait in ipad and iPhone.
[In Android we used fill parent to autoresize dynamically created UI-Elements.is there any syntax in iOS to autoresizing]
How to UI-elements create dynamically supports both Landscape mode and portrait mode?
How create the view controller to support the Landscape mode and portrait mode?
Is there required to create a all views and UI-elements dynamically?
1)If you will make xib or nib than develop xib or nib in only one mode as portrait or landscape. Than use Autoresizing option as below Image for any control.
http://www.raywenderlich.com/50319/beginning-auto-layout-tutorial-in-ios-7-part-2. You can use this link for auto layout.
But Auto layout is not work properly as you want. so u need to set frames of control pro grammatically evenif u r using autolayout.
2) And If you want to develop dynamically than using below code you can set frame of all controls.
In ViewwillAppear.
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter]addObserver:self selector:#selector(orientationChanged) name:UIDeviceOrientationDidChangeNotification object:nil];
in viewdidload
set your controls as below.
UILabel lbl - [UILabel alloc]init];
-(void)orientationChanged{
if(Orientation is portrait){
[lbl setFrame:for portrait];
}else{
[lbl setFrame: for landscape];
}
If device change mode than above notification fire and in that method. you can set frame of control.
I hope you will get your answer.
You can use auto - layout for providing both the portrait and landscape mode.
For more details, check this : What is Auto Layout?.
You have to set the constraints for landscape and portrait mode to work. Like if you want a button at the top, you can set constraints on it : from top and left and so on.
If you want a UI element to work change dynamically, you just need to change frame on orientation as per your requirement. Sample code is here :
# pragma mark - Orientation related methods
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration NS_AVAILABLE_IOS(3_0)
{
if (toInterfaceOrientation == UIInterfaceOrientationLandscapeLeft) {
[self deviceRotatedToLandscapeMode];
}
else if (toInterfaceOrientation == UIInterfaceOrientationLandscapeRight) {
[self deviceRotatedToLandscapeMode];
}
else if (toInterfaceOrientation == UIInterfaceOrientationPortrait) {
[self deviceRotatedToPortraitMode];
}
else if (toInterfaceOrientation == UIInterfaceOrientationPortraitUpsideDown) {
[self deviceRotatedToPortraitMode];
}
}
- (void) deviceRotatedToPortraitMode {
self.mTableView.frame = CGRectMake(0.0, 0.0, self.view.frame.size.width, self.view.frame.size.height);
}
- (void) deviceRotatedToLandscapeMode {
self.mTableView.frame = CGRectMake(0.0, 0.0, self.view.frame.size.height, self.view.frame.size.height);
}
The most reliable approach -
Create a method in your view controller -
-(void)setFrameForOrientationChange:(UIDeviceOrientation*) o {
//...
implement your code for frame settings..
}
We can create formsheet views of non-standard size using the following bit of code (below), but the resulting view isn't centered -- the x-coordinate is always where a standard sized formsheet view would be. Changing the center property of the view and superview doesn't affect anything in a useful way. How can we use a custom formsheet size that is correctly centered?
Add the following code to the view controller that is being presented as a UIModalPresentationPageSheet:
#implementation MySpecialFormsheet {
CGRect _realBounds;
}
- (void)viewDidLoad {
// code below works great, but the resulting view isn't centered.
_realBounds = self.view.bounds;
[super viewDidLoad];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.view.superview.bounds = _realBounds;
}
I've only found success changing the frame after presenting it. I actually do it like so:
GameSetupViewController *gameSetup = [[GameSetupViewController alloc] init];
[gameSetup setDelegate:self];
[gameSetup setModalPresentationStyle:UIModalPresentationPageSheet];
[self presentModalViewController:gameSetup animated:YES];
[gameSetup.view.superview setAutoresizingMask:(UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth)];
[gameSetup.view.superview setFrame:CGRectMake(64, 64, 896, 640)];
But perhaps you could get away with using viewDidAppear or some other handle.
I have an iPad app that I would like to work in the sideways orientation instead of just portrait. I have programatically placed images, labels, and buttons into my view and used CGRectMake (x,x,x,x) to tell them where to go on the view into the center. When the app rotates horizontally, I need my labels and buttons to shift up (since they can't go down as far when in landscape mode), but stay in the center. Here is some code I've been playing with:
if((self.interfaceOrientation == UIDeviceOrientationLandscapeLeft) || (self.interfaceOrientation == UIDeviceOrientationLandscapeRight))
{
lblDate = [[UILabel alloc] initWithFrame:CGRectMake(384-(fieldWidth/2)-30,controlTop+45,120,40)]; //these dimensions aren't correct, though they don't matter here
lblDate.text = #"Date:";
lblDate.backgroundColor = [UIColor clearColor];
[contentView addSubview:lblDate];
} else {
//the orientation must be portrait or portrait upside down, so put duplicate the above code and change the pixel dimensions
}
Thanks for your help!
Take a look at this: iphone/ipad orientation handling
You just specify each control location depending on the rotation.
I know this might be a bit of an old question now looking to the date, but I just very recently faced the same problem. You could stumble upon many suggestions such as transforming main view's subviews or it's layers. Non of this worked for me.
Actually the solitary solution I've found is that since you want your UI controls to be located dynamically then don't deploy them mainly in the interface builder. The interface builder can be helpful knowing the desired locations for dynamic controls in both portrait and landscape orientations. i.e make two separate test views in the interface builder, one portrait and the other landscape, align your controls as you wish and right down X, Y, Width and Height data just to use with CGRectMake for each control.
As soon as you write down all needed positioning data from the interface builder get rid of those already drawn controls and outlets/actions links. They will be of no need now.
Of course don't forget to implement UIViewController's willRotateToInterfaceOrientation to set control's frame with each orientation change.
#interface
//Declare your UI control as a property of class.
#property (strong, nonatomic) UITableView *myTable;
#end
#implementation
// Synthesise it
#synthesize myTable
- (void)viewDidLoad
{
[super viewDidLoad];
// Check to init for current orientation, don't use [UIDevice currentDevice].orientation
if (self.interfaceOrientation == UIInterfaceOrientationLandscapeLeft || self.interfaceOrientation == UIInterfaceOrientationLandscapeRight)
{
myTable = [[UITableView alloc] initWithFrame:CGRectMake(20, 20, 228, 312)];
}
else if (self.interfaceOrientation == UIInterfaceOrientationPortrait)
{
myTable = [[UITableView alloc] initWithFrame:CGRectMake(78, 801, 307, 183)];
}
}
myTable.delegate = self;
myTable.dataSource = self;
[self.view addSubview:myTable];
}
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
if (toInterfaceOrientation == UIInterfaceOrientationLandscapeRight || toInterfaceOrientation == UIInterfaceOrientationLandscapeLeft)
{
// Show landscape
myTable.frame = CGRectMake(20, 20, 228, 312);
}
else
{
// Show portrait
myTable.frame = CGRectMake(78, 801, 307, 183);
}
}