Автоматическое определение размера UIView после добавления в окно
Примечание. Это может быть дубликат Subview Doesnt AutoSize при добавлении в контроллер корневого представления
У меня есть приложение для iPad, которое переключается между разными видами в главном окне. Код переключения вида выглядит следующим образом:
- (void)switchToViewController:(UIViewController*)viewController {
if (currentViewController != viewController) {
[currentViewController.view removeFromSuperview];
currentViewController = viewController;
[window addSubview:viewController.view];
}
}
Проблема заключается в том, что, когда новый вид (UISplitView) появляется в альбомной ориентации, он не имеет размера для заполнения всего окна. Справа - большое пустое черное пространство. Похоже, что представление имеет ширину всего 768 пикселей, а не 1024-пиксельную ширину ландшафтного окна.
Если я повернул устройство к портрету, а затем вернулся к пейзажу, вид будет выглядеть правильно.
Если устройство находится в портретной ориентации, все работает нормально. UISplitView также получает правильный размер, если это первый вид, который я показываю. Проблема возникает только в том случае, если я переключаюсь на нее после отображения другого вида в ландшафте.
Итак, есть ли способ заставить iPhone OS изменить размер представления после его добавления в окно?
Я пробовал звонить sizeToFit
и setNeedsLayout
. Я также попытался настроить представление bounds
на окно bounds
, и я попытался настроить frame
на соответствие предыдущему кадру представления.
Ответы
Ответ 1
Это работает, но кажется немного взломанным:
- (void)switchToViewController:(UIViewController *)viewController {
if (viewController != currentViewController) {
UIInterfaceOrientation orientation = currentViewController.interfaceOrientation;
[currentViewController.view removeFromSuperview];
currentViewController = viewController;
UIView *view = viewController.view;
// Set appropriate view frame (it won't be autosized by addSubview:)
CGRect appFrame = [[UIScreen mainScreen] applicationFrame];
if (UIInterfaceOrientationIsLandscape(orientation)) {
// Need to flip the X-Y coordinates for landscape
view.frame = CGRectMake(appFrame.origin.y, appFrame.origin.x, appFrame.size.height, appFrame.size.width);
}
else {
view.frame = appFrame;
}
[window addSubview:view];
}
}
Ответ 2
Это абсолютно возможно!: -)
Вы можете проверить мое репо здесь:
https://github.com/hfossli/AGWindowView
Он будет автоматически обрабатывать любые вращения и рамки, поэтому вам не придется беспокоиться об этом.
Если вам нравится беспокоиться об этом, вы можете просто вырезать и вставлять наиболее важные части
# 1 Добавить представление в окно
[[UIApplication sharedApplication] keyWindow] addSubview:aView];
# 2 Добавить просмотр и просмотр обновлений
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(statusBarFrameOrOrientationChanged:) name:UIApplicationDidChangeStatusBarOrientationNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(statusBarFrameOrOrientationChanged:) name:UIApplicationDidChangeStatusBarFrameNotification object:nil];
Не забудьте удалить прослушивание уведомлений
[[NSNotificationCenter defaultCenter] removeObserver:self];
# 3 Сделайте математику
- (void)statusBarFrameOrOrientationChanged:(NSNotification *)notification
{
/*
This notification is most likely triggered inside an animation block,
therefore no animation is needed to perform this nice transition.
*/
[self rotateAccordingToStatusBarOrientationAndSupportedOrientations];
}
- (void)rotateAccordingToStatusBarOrientationAndSupportedOrientations
{
UIInterfaceOrientation statusBarOrientation = [UIApplication sharedApplication].statusBarOrientation;
CGFloat angle = UIInterfaceOrientationAngleOfOrientation(statusBarOrientation);
CGFloat statusBarHeight = [[self class] getStatusBarHeight];
CGAffineTransform transform = CGAffineTransformMakeRotation(angle);
CGRect frame = [[self class] rectInWindowBounds:self.window.bounds statusBarOrientation:statusBarOrientation statusBarHeight:statusBarHeight];
[self setIfNotEqualTransform:transform frame:frame];
}
- (void)setIfNotEqualTransform:(CGAffineTransform)transform frame:(CGRect)frame
{
if(!CGAffineTransformEqualToTransform(self.transform, transform))
{
self.transform = transform;
}
if(!CGRectEqualToRect(self.frame, frame))
{
self.frame = frame;
}
}
+ (CGFloat)getStatusBarHeight
{
UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;
if(UIInterfaceOrientationIsLandscape(orientation))
{
return [UIApplication sharedApplication].statusBarFrame.size.width;
}
else
{
return [UIApplication sharedApplication].statusBarFrame.size.height;
}
}
+ (CGRect)rectInWindowBounds:(CGRect)windowBounds statusBarOrientation:(UIInterfaceOrientation)statusBarOrientation statusBarHeight:(CGFloat)statusBarHeight
{
CGRect frame = windowBounds;
frame.origin.x += statusBarOrientation == UIInterfaceOrientationLandscapeLeft ? statusBarHeight : 0;
frame.origin.y += statusBarOrientation == UIInterfaceOrientationPortrait ? statusBarHeight : 0;
frame.size.width -= UIInterfaceOrientationIsLandscape(statusBarOrientation) ? statusBarHeight : 0;
frame.size.height -= UIInterfaceOrientationIsPortrait(statusBarOrientation) ? statusBarHeight : 0;
return frame;
}
CGFloat UIInterfaceOrientationAngleOfOrientation(UIInterfaceOrientation orientation)
{
CGFloat angle;
switch (orientation)
{
case UIInterfaceOrientationPortraitUpsideDown:
angle = M_PI;
break;
case UIInterfaceOrientationLandscapeLeft:
angle = -M_PI_2;
break;
case UIInterfaceOrientationLandscapeRight:
angle = M_PI_2;
break;
default:
angle = 0.0;
break;
}
return angle;
}
UIInterfaceOrientationMask UIInterfaceOrientationMaskFromOrientation(UIInterfaceOrientation orientation)
{
return 1 << orientation;
}
Удачи!
Ответ 3
Окно может включать в себя другие элементы пользовательского интерфейса помимо вашего представления. Разница в 20 пикселей в вашем примере - это высота строки состояния.
[[UIApplication sharedApplication] statusBarFrame].height;
Ни окно, ни экран не вращаются. Получение их кадров и их использование для повернутого представления будут работать, только если вы изменили высоту и ширину.
Если вы используете UIViewController, попробуйте вернуть YES из этого метода:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation; // Override to allow rotation. Default returns YES only for UIDeviceOrientationPortrait
Ответ 4
У меня такая же проблема, но я исправил ее с помощью этих строк кода:
- (void)changeRow:(NSNotification *)notification {
[window addSubview:new.view];
[old.view removeFromSuperview];
[new.view removeFromSuperview];
[window addSubview:new.view];
}
Вы должны добавить новый вид, затем удалить старый и новый, а затем добавить новое представление. Я не знаю почему, но это работает.
Ответ 5
Ответ Fossli правильный для iPad. Однако у меня есть универсальное приложение, которое мне нужно было поддерживать. Поэтому необходимы некоторые корректировки.
Добавьте в AppDelegate.h следующее:
@property (strong, nonatomic) UIImageView *imageView;
Добавьте в AppDelegate.m следующее:
@synthesize imageView;
- (void)orientationChanged:(NSNotification *)notification
{
UIDeviceOrientation deviceOrientation = [UIDevice currentDevice].orientation;
if (! (UIInterfaceOrientationIsLandscape(deviceOrientation) ||
UIInterfaceOrientationIsPortrait(deviceOrientation)))
{
// May be "UIInterfaceOrientationUnknown" which does not appear to be a defined value anywhere.
return;
}
[imageView setImage:[UIImage imageNamed:[Utility getBackgroundImageNameWithOrientation:deviceOrientation]]];
/*
iOS Image Sizes
iPhone/iPod Portrait 320 x 480 (640 x 960 @2x)
iPad Portrait 768 x 1004 (1536 x 2008 @2x)
Landscape 1024 x 748 (2048 x 1496 @2x)
iPad window bounds in both orientations 768 x 1024 (needs manual swap in landscape)
iPhone window bounds in both orientations 320 x 480 (needs manual swap in landscape)
Note the size variations between the required default launch image sizes and
the size of the window bounds.
iPhone/iPod only requires rotations.
iPad needs origin or size adjustments depending on orientation.
*/
CGFloat angle = 0.0;
CGRect newFrame = [[self window] bounds];
// How to get size of status bar
// Size of status bar gets all wonky on rotations so just set it manually
// CGSize statusBarSize = [[UIApplication sharedApplication] statusBarFrame].size;
CGSize statusBarSize = CGSizeMake(20.0, 20.0);
if (deviceOrientation == UIInterfaceOrientationPortraitUpsideDown)
{
angle = M_PI;
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad)
{
newFrame.size.height -= statusBarSize.height;
}
}
else if (deviceOrientation == UIInterfaceOrientationLandscapeLeft)
{
angle = - M_PI / 2.0f;
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad)
{
newFrame.origin.x += statusBarSize.height;
newFrame.size.width += statusBarSize.height;
}
}
else if (deviceOrientation == UIInterfaceOrientationLandscapeRight)
{
angle = M_PI / 2.0f;
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad)
{
newFrame.size.width -= statusBarSize.height;
}
}
else
{
angle = 0.0;
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad)
{
newFrame.origin.y += statusBarSize.height;
newFrame.size.height -= statusBarSize.height;
}
}
imageView.transform = CGAffineTransformMakeRotation(angle);
imageView.frame = newFrame;
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Add background image to window with orientation changes so that it is visible in all views.
// A listener is added since subviews do not receive orientation changes.
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(orientationChanged:) name:UIDeviceOrientationDidChangeNotification object: nil];
UIDeviceOrientation deviceOrientation = [UIDevice currentDevice].orientation;
imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:[Utility getBackgroundImageNameWithOrientation:deviceOrientation]]];
[[self window] addSubview:imageView];
return YES;
}
Добавьте в Utility.h
следующее:
+ (NSString *)getBackgroundImageNameWithOrientation:(UIDeviceOrientation)interfaceOrientation;
Добавьте в Utility.m следующее:
+ (NSString *)getBackgroundImageNameWithOrientation:(UIDeviceOrientation)interfaceOrientation
{
NSString *imageName = nil;
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad)
{
if (UIInterfaceOrientationIsLandscape(interfaceOrientation))
{
imageName = @"Default-Landscape~ipad.png";
}
else
{
imageName = @"Default-Portrait~ipad.png";
}
}
else
{
if (UIInterfaceOrientationIsLandscape(interfaceOrientation))
{
imageName = @"Default-Landscape~iphone.png";
}
else
{
imageName = @"Default.png";
}
}
return imageName;
}
Ответ 6
Windows iOS7 имеют разные типы поведения с окнами iOS8/9.
Окно клавиатуры iOS7 и всех окон iOS8/9 всегда имеет правильную ориентацию и размер. Таким образом, вы можете наблюдать за событиями изменения размера и обновлять фрейм вашего представления.
Но другие окна iOS7 всегда сохраняют ориентацию и размер портрета. После вращения вам понадобится преобразование обновления.
Вам необходимо наблюдать UIApplicationWillChangeStatusBarOrientationNotification и размер обновления вашего UIView следующим образом:
@interface MyView : UIView
@end
@implementation MyView
- (instancetype)init
{
if (self = [super init]) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(changeOrientationHandler:) name:UIApplicationWillChangeStatusBarOrientationNotification object:nil];
}
return self;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillChangeStatusBarOrientationNotification object:nil];
}
- (void)updateTransformWithOrientation:(UIInterfaceOrientation)orientation
{
CGFloat width = CGRectGetWidth(self.window.bounds);
CGFloat height = CGRectGetHeight(self.window.bounds);
if (width > height) {
CGFloat temp = width;
width = height;
height = temp;
}
CGFloat offset = (height - width) / 2;
CGAffineTransform transform;
switch (orientation) {
case UIInterfaceOrientationLandscapeLeft:
transform = CGAffineTransformMakeTranslation(-offset, offset);
transform = CGAffineTransformRotate(transform, -M_PI_2);
break;
case UIInterfaceOrientationLandscapeRight:
transform = CGAffineTransformMakeTranslation(-offset, offset);
transform = CGAffineTransformRotate(transform, M_PI_2);
break;
case UIInterfaceOrientationPortraitUpsideDown:
transform = CGAffineTransformMakeRotation(-M_PI);
break;
default:
transform = CGAffineTransformIdentity;
break;
}
self.transform = transform;
self.frame = CGRectMake(0, 0, width, height);
}
- (void)updateFrameWithOrientation:(UIInterfaceOrientation)orientation
{
CGFloat width = CGRectGetWidth(self.window.bounds);
CGFloat height = CGRectGetHeight(self.window.bounds);
if (width > height) {
CGFloat temp = width;
width = height;
height = temp;
}
switch (orientation) {
case UIInterfaceOrientationLandscapeLeft:
case UIInterfaceOrientationLandscapeRight:
self.frame = CGRectMake(0, 0, height, width);
break;
default:
self.frame = CGRectMake(0, 0, width, height);
break;
}
}
- (void)updateWithOrientation:(UIInterfaceOrientation)orientation
{
BOOL isIos7 = [[UIDevice currentDevice].systemVersion floatValue] < 8.0;
BOOL isKeyboardWindow = [self.window isKindOfClass:NSClassFromString(@"UITextEffectsWindow")];
if (isIos7 == YES && isKeyboardWindow == NO) {
[self updateTransformWithOrientation:orientation];
} else {
[self updateFrameWithOrientation:orientation];
}
}
- (void)changeOrientationHandler:(NSNotification *)notification
{
[UIView animateWithDuration:0.25 animations:^{
UIInterfaceOrientation orientation = (UIInterfaceOrientation)[notification.userInfo[UIApplicationStatusBarOrientationUserInfoKey] integerValue];
[self updateWithOrientation:orientation];
}];
}
@end