仅使用一个 .xib 文件的用于纵向和横向的替代 iOS 布局
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/18595561/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Alternative iOS layouts for portrait and landscape using just one .xib file
提问by Dave Haigh
Using interface builder in xcode and just one .xib file, how can I create alternate layouts when rotating between landscape and portrait orientations?
在 xcode 中使用界面构建器和一个 .xib 文件,如何在横向和纵向之间旋转时创建替代布局?
See diagram of differing layouts
查看不同布局的图表
N.b. the green view/area would contain 3 items flowing horizontal in landscape and in portrait those 3 items would flow vertically within the green view/area.
铌。绿色视图/区域将包含横向横向流动的 3 个项目,纵向流动时,这 3 个项目将在绿色视图/区域内垂直流动。
回答by MeXx
A way to do this is to have three views in your .xib file. The first one is the normal view of your Viewcontroller with no subviews.
一种方法是在 .xib 文件中包含三个视图。第一个是您的 Viewcontroller 的普通视图,没有子视图。
Then you create the views for portrait and landscape as you need them. All three have to be root-level views (have a look at the screenshot)
然后根据需要创建纵向和横向视图。所有三个都必须是根级视图(查看屏幕截图)
In your Viewcontroller, create 2 IBOutlets, one for the portrait and one for the landscape view and connect them with the corresponding views in the interface builder:
在您的 Viewcontroller 中,创建 2 个 IBOutlets,一个用于纵向视图,一个用于横向视图,并将它们与界面构建器中的相应视图连接起来:
IBOutlet UIView *_portraitView;
IBOutlet UIView *_landscapeView;
UIView *_currentView;
The third view, _currentView
is needed to keep track of which one of these views is currently being displayed.
Then create a new function like this:
_currentView
需要第三个视图来跟踪当前正在显示这些视图中的哪一个。然后创建一个像这样的新函数:
-(void)setUpViewForOrientation:(UIInterfaceOrientation)orientation
{
[_currentView removeFromSuperview];
if(UIInterfaceOrientationIsLandscape(orientation))
{
[self.view addSubview:_landscapeView];
_currentView = _landscapeView;
}
else
{
[self.view addSubview:_portraitView];
_currentView = _portraitView;
}
}
You will need to call this function from two different places, first for initialization:
你需要从两个不同的地方调用这个函数,首先是为了初始化:
-(void)viewDidLoad
{
[super viewDidLoad];
UIInterfaceOrientation interfaceOrientation = [[UIApplication sharedApplication] statusBarOrientation];
[self setUpViewForOrientation:interfaceOrientation];
}
And second for orientation changes:
其次是方向变化:
-(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
[self setUpViewForOrientation:toInterfaceOrientation];
}
Hope that helps you!
希望能帮到你!
回答by Grzegorz Krukowski
There is no automatic way to support that since it is against Apple design. You should have one ViewController supporting both orientations.
没有自动的方法来支持它,因为它违背了 Apple 的设计。您应该有一个支持两种方向的 ViewController。
But if you really want to do so you need to reload xib's on rotation events and load different nib files.
但是如果你真的想这样做,你需要在旋转事件上重新加载 xib 并加载不同的 nib 文件。
-(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
[[NSBundle mainBundle] loadNibNamed:[self nibNameForInterfaceOrientation:toInterfaceOrientation] owner:self options:nil];
[self viewDidLoad];
}
- (NSString*) nibNameForInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
NSString *postfix = (UIInterfaceOrientationIsLandscape(interfaceOrientation)) ? @"portrait" : @"landscape";
return [NSString stringWithFormat:@"%@-%@", NSStringFromClass([self class]), postfix];
}
And you create two nib files post-fixed with "landscape" and "portrait".
并且您创建了两个使用“横向”和“纵向”进行后期修复的 nib 文件。
回答by TomSwift
I like @MeXx's solution, but it has the overhead of keeping two different view hierarchies in memory. Also, if any subview has state (e.g. color) that changes, you'll need to map that across when you switch hierarchies.
我喜欢@MeXx 的解决方案,但它有在内存中保留两个不同视图层次结构的开销。此外,如果任何子视图的状态(例如颜色)发生变化,则您需要在切换层次结构时将其映射。
Another solution might be to use auto-layout and swap the constraints for each subview for each orientation. This would work best if you have a 1:1 mapping between subviews in both orientations.
另一种解决方案可能是使用自动布局并为每个方向的每个子视图交换约束。如果您在两个方向的子视图之间都有 1:1 的映射,这将最有效。
Understandably you want to use IB to visually define the layout for each orientation. Under my plan you'd have to do what @MeXx prescribes, but then create a mechanism to store both sets of constraints once the nib was loaded (awakeFromNib
) and re-apply the correct set on layout (viewWillLayoutSubviews
). You could throw away the secondary view hierarchy once you scraped and stored its constraints. (Since constraints are view-specific you'd likely be creating new constraints to apply to the actual subviews).
可以理解的是,您希望使用 IB 来直观地定义每个方向的布局。根据我的计划,您必须按照@MeXx 的规定执行操作,然后创建一种机制来在加载笔尖后存储两组约束 ( awakeFromNib
) 并在布局 ( viewWillLayoutSubviews
)上重新应用正确的设置。一旦你抓取并存储了它的约束,你就可以扔掉辅助视图层次结构。(由于约束是特定于视图的,您可能会创建新的约束以应用于实际的子视图)。
Sorry I don't have any code. It's just a plan at this stage.
抱歉,我没有任何代码。这只是现阶段的一个计划。
Final note - this would all be easier with a xib than a storyboard since in a storyboard it's painful to describe views that live outside of a view controller's main view (which is desirable since otherwise its a PITA to edit). Blach!
最后一点 - 使用 xib 比使用故事板更容易,因为在故事板中描述位于视图控制器主视图之外的视图是很痛苦的(这是可取的,否则它是一个 PITA 需要编辑)。布拉赫!
回答by kshitij godara
Ya you can sir surely ....
是的,您当然可以,先生......
I used to do is by giving frame manually when Device rotates
我以前做的是在设备旋转时手动给出框架
Whenever device rotates , - (void)viewWillLayoutSubviews
get called
每当设备旋转时,- (void)viewWillLayoutSubviews
就会被调用
for Example - take a UIButton *button
in your UIView
,
例如 -UIButton *button
在你的UIView
,
- (void)viewWillLayoutSubviews
{
if (UIDeviceOrientationIsLandscape([self.view interfaceOrientation]))
{
//x,y as you want
[ button setFrame:CGRectMake:(x,y,button.width,button.height)];
}
else
{
//In potrait
//x,y as you want
[ button setFrame:CGRectMake:(x,y,button.width,button.height)];
}
}
In this way you can place it as you like . Thanks
这样您就可以随心所欲地放置它。谢谢
Please have a look on these images
请看看这些图片
First image of my xCode XIB
UIView which i want in both screen
我XIB
在两个屏幕中都想要的xCode UIView 的第一张图片
Now as i want to look this view in landscape too so i went to edit my -(void)viewWillLayoutSubviews
现在因为我也想在风景中看这个视图所以我去编辑我的 -(void)viewWillLayoutSubviews
Now the result is like this First image of potrait Screen in Simulator
现在的结果是这样 模拟器中的 potrait Screen 的第一张图像
and this is in Landscape
这是在风景中
回答by Prcela
Solution with storyboard [ios7]. Inside the main view controller, there is the container view that can include the tab bar controller. Inside the tab bar controller, there are 2 tabs. One tab is for controller with portrait and another is for controller in landscape mode.
带有故事板的解决方案 [ios7]。在主视图控制器内部,有一个容器视图,可以包含标签栏控制器。在标签栏控制器内部,有 2 个标签。一个选项卡用于纵向模式的控制器,另一个选项卡用于横向模式的控制器。
The main view controller implements these functions:
主视图控制器实现了这些功能:
#define INTERFACE_ORIENTATION() ([[UIApplication sharedApplication]statusBarOrientation])
@interface MainViewController ()
@property(nonatomic,weak) UITabBarController *embeddedTabBarController;
@end
@implementation MainViewController
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:@"embedContainer"])
{
self.embeddedTabBarController = segue.destinationViewController;
[self.embeddedTabBarController.tabBar setHidden:YES];
}
}
- (void) adaptToOrientation:(UIInterfaceOrientation)interfaceOrientation
{
if (UIInterfaceOrientationIsLandscape(interfaceOrientation))
{
[self.embeddedTabBarController setSelectedIndex:1];
}
else
{
[self.embeddedTabBarController setSelectedIndex:0];
}
}
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation duration:(NSTimeInterval)duration
{
[self adaptToOrientation:interfaceOrientation];
}
- (void) viewWillAppear:(BOOL)animated
{
[self adaptToOrientation:INTERFACE_ORIENTATION()];
}
The segue link in storyboard should be named "embedContainer".
故事板中的 segue 链接应命名为“embedContainer”。
Assure that container view is resizable to fulfil the parent view, in both cases, landscape and portrait.
在横向和纵向两种情况下,确保容器视图可调整大小以满足父视图。
Take care about the fact that in the runtime 2 instances of the same controller lives in the same time.
注意在运行时同一控制器的 2 个实例同时存在的事实。
回答by rocket101
I'd just like to add a new answer to reflect upcoming new features in ios8 and XCode 6.
我只想添加一个新答案以反映 ios8 和 XCode 6 中即将推出的新功能。
In the latest new software, Apple introduced size classes, which enable the storyboard to intelligently adapt based on what screen size and orientation you are using. Though I suggest you look at the docs above or the WWDC 2014 session building adaptive apps with UIKit, I'll try to paraphrase.
在最新的新软件中,Apple 引入了size classes,它使故事板能够根据您使用的屏幕尺寸和方向进行智能调整。尽管我建议您查看上面的文档或使用 UIKit 构建自适应应用程序的 WWDC 2014 会话,但我会尝试解释一下。
Ensure size classes are enabled,.
You will notice your storyboard files are now square. You can set up the basic interface here. The interface will be intelligently resized for all enabled devices and orientation.
您会注意到您的故事板文件现在是方形的。您可以在此处设置基本界面。界面将针对所有启用的设备和方向智能调整大小。
At the bottom of the screen, you will see a button saying yAny xAny. By clicking this, you can modify just one size class, for instance, landscape and portrait.
在屏幕底部,您会看到一个按钮,上面写着 yAny xAny。单击此按钮,您可以只修改一种尺寸等级,例如横向和纵向。
I do suggest that you read the docs above, but I hope this helps you, as it uses only one storyboard.
我建议你阅读上面的文档,但我希望这对你有帮助,因为它只使用一个故事板。
回答by Christian Fries
Resize Programatically
以编程方式调整大小
I have an app where I have exactly the same situation. In my NIB I just haven the portrait layout. The other layout is performed programatically. It is not that difficult to specify a few sizes programmatically, so no need to maintain two NIBs.
我有一个应用程序,我的情况完全相同。在我的笔尖中,我只有纵向布局。另一种布局以编程方式执行。以编程方式指定几个大小并不困难,因此无需维护两个 NIB。
Note that performing the view change programatically by setting frames will result in an animation (or can easily be made using an animation). This will give the user a valuable feedback about which component moved to which position. If you just load a second view, you will lose this advantage and from a UI perspective I believe loading an alternative view is not a good solution.
请注意,通过设置帧以编程方式执行视图更改将产生动画(或者可以使用动画轻松制作)。这将为用户提供有关哪个组件移动到哪个位置的宝贵反馈。如果你只是加载第二个视图,你将失去这个优势,从 UI 的角度来看,我相信加载一个替代视图不是一个好的解决方案。
Use two ViewControllers
使用两个 ViewController
If you like to use two ViewControllers, then Apple describes how to do it in its developer documentation, i.e., you find the answer to you question here: RespondingtoDeviceOrientationChanges.
如果您喜欢使用两个 ViewController,那么 Apple 在其开发人员文档中描述了如何使用它,即您可以在这里找到问题的答案:RespondingtoDeviceOrientationChanges。
If you like to do it programatically you may add re-position code in the method didRotateFromInterfaceOrientation
.
如果您喜欢以编程方式执行此操作,您可以在方法中添加重新定位代码didRotateFromInterfaceOrientation
。
Example
例子
I added the method to my ViewController:
我将该方法添加到我的 ViewController 中:
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
[self adjustLayoutToOrientation];
}
Then implemented the adjustment of the layout. Here is an example (the code is specific to my app, but you can read sizes of superviews and calculate frames of subviews from it.
然后实施了布局的调整。这是一个示例(代码特定于我的应用程序,但您可以读取超级视图的大小并从中计算子视图的帧。
- (void)adjustLayoutToOrientation
{
UIInterfaceOrientation toInterfaceOrientation = self.interfaceOrientation;
bool isIPhone = !([[UIDevice currentDevice] respondsToSelector:@selector(userInterfaceIdiom)] && [[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad);
[self adjustViewHeight];
[self adjustSubviewsToFrame: [mailTemplateBody.superview.superview frame]];
CGRect pickerViewWrapperFrame = pickerViewWrapper.frame;
if(isIPhone) {
pickerViewWrapperFrame.origin.x = 0;
pickerViewWrapperFrame.size.height = keyboardHeight;
pickerViewWrapperFrame.origin.y = [self view].frame.size.height-pickerViewWrapperFrame.size.height;
pickerViewWrapperFrame.size.width = [self view].frame.size.width;
}
else {
pickerViewWrapperFrame = pickerView.frame;
}
// MORE ADJUSTMENTS HERE
}