IOS: панель навигации titleView не изменяет размер правильно, когда телефон вращается

Я пишу приложение для iPhone, которое (как и большинство приложений) поддерживает автоматическое вращение: вы поворачиваете свой телефон, и его виды вращаются и изменяются соответствующим образом.

Но я назначаю пользовательское представление navigationItem.titleView (область заголовка панели навигации), и я не могу получить это представление для правильного изменения размера, когда телефон вращается.

Я знаю, что вы думаете: "Просто установите его autoresizingMask на UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight", но это не так просто. Конечно, если я не установлю свой взгляд autoresizingMask, то мое представление не изменится; и я хочу, чтобы он изменялся.

Проблема заключается в том, что если я устанавливаю его autoresizingMask, то он правильно изменяется, пока это вид будет видимым; но размер titleView становится испорченным в этом сценарии:

  • Запустите приложение, удерживая телефон в портретном режиме. Все выглядит хорошо.
  • Сделайте что-то, что заставляет приложение нажимать другое представление на стек навигации. Например. щелкните строку таблицы или кнопку, вызывающую вызов [self.navigationController pushViewController:someOtherViewController animated:YES].
  • Во время просмотра дочернего контроллера поверните телефон в положение "пейзаж".
  • Нажмите кнопку "Назад", чтобы вернуться к просмотру верхнего уровня. На данный момент вид заголовка перепутался: хотя вы держите телефон в альбомном режиме, название заголовка по-прежнему имеет размер, как если бы вы держали его в портретном режиме.
  • Наконец, поверните телефон обратно в портретный режим. Теперь ситуация становится еще хуже: размер заголовка уменьшается (поскольку панель навигации уменьшилась), но поскольку она уже слишком мала, теперь она слишком мала.

Если вы хотите воспроизвести это самостоятельно, выполните следующие действия (это немного работает):

  • Создайте приложение, используя мастер Xcode "Навигационное приложение".
  • Установите его так, чтобы в представлении таблицы верхнего уровня были строки, которые при нажатии на них нажимают подробный вид на стек навигации.
  • Включите этот код как в контроллер верхнего уровня, так и в контроллер подробного представления:

    - (BOOL)shouldAutorotateToInterfaceOrientation:
            (UIInterfaceOrientation)interfaceOrientation {
        return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
    }
    
  • Включите этот код только в контроллер верхнего уровня:

    - (void)viewDidLoad {
        [super viewDidLoad];
    
        // Create "Back" button
        UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithTitle:@"Master"
            style:UIBarButtonItemStylePlain target:nil action:nil];
        self.navigationItem.backBarButtonItem = backButton;
        [backButton release];
    
        // Create title view
        UILabel* titleView = [[[UILabel alloc] initWithFrame:CGRectMake(0,0,500,38)] autorelease];
        titleView.textAlignment = UITextAlignmentCenter;
        titleView.text = @"Watch this title view";
    
        // If I leave the following line turned on, then resizing of the title view
        // messes up if I:
        //
        // 1. Start at the master view (which uses this title view) in portrait
        // 2. Navigate to the detail view
        // 3. Rotate the phone to landscape
        // 4. Navigate back to the master view
        // 5. Rotate the phone back to portrait
        //
        // On the other hand, if I remove the following line, then I get a different
        // problem: The title view doesn't resize as I want it to when I:
        //
        // 1. Start at the master view (which uses this title view) in portrait
        // 2. Rotate the phone to landscape
        titleView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    
        self.navigationItem.titleView = titleView;
    }
    
  • Наконец, следуйте моим инструкциям.

Итак... я делаю что-то неправильно? Есть ли способ сделать мой TitleView всегда правильно измененным?

Ответы

Ответ 1

(Отвечая на мой собственный вопрос)

Я получил эту работу, вручную отслеживая поля titleView (его расстояние от краев панели навигации) - сохранение при исчезновении представления и восстановление при появлении вида.

Идея заключается в том, что мы не восстанавливаем titleView до точного размера, который был ранее; скорее, мы восстанавливаем его так, чтобы он имел те же поля, что и раньше. Таким образом, если телефон повернулся, заголовок будет иметь новый, соответствующий размер.

Вот мой код:

В моем представлении файл .h htt:

@interface MyViewController ...
{
    CGRect titleSuperviewBounds;
    UIEdgeInsets titleViewMargins;
}

На мой взгляд, файл .m файла:

/**
 * Helper function: Given a parent view bounds and a child view frame,
 * calculate the margins of the child view.
 */
- (UIEdgeInsets) calcMarginsFromParentBounds:(CGRect)parentBounds
                                  childFrame:(CGRect)childFrame {
    UIEdgeInsets margins;
    margins.left = childFrame.origin.x;
    margins.top = childFrame.origin.y;
    margins.right = parentBounds.size.width -
        (childFrame.origin.x + childFrame.size.width);
    margins.bottom = parentBounds.size.height -
        (childFrame.origin.y + childFrame.size.height);
    return margins;
}

- (void)viewDidUnload {
    [super viewDidUnload];

    titleSuperviewBounds = CGRectZero;
    titleViewMargins = UIEdgeInsetsZero;
}

- (void) viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];

    // Keep track of bounds information, so that if the user changes the
    // phone orientation while we are in a different view, then when we
    // return to this view, we can fix the titleView size.
    titleSuperviewBounds = self.navigationItem.titleView.superview.bounds;
    CGRect titleViewFrame = self.navigationItem.titleView.frame;
    titleViewMargins = [self calcMarginsFromParentBounds:titleSuperviewBounds
                                              childFrame:titleViewFrame];
}


- (void) viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];

    // Check for the case where the user went into a different view, then
    // changed the phone orientation, then returned to this view.  In that
    // case, our titleView probably has the wrong size, and we need to fix it.
    if (titleSuperviewBounds.size.width > 0) {
        CGRect newSuperviewBounds =
            self.navigationItem.titleView.superview.bounds;
        if (newSuperviewBounds.size.width > 0 &&
            !CGRectEqualToRect(titleSuperviewBounds, newSuperviewBounds))
        {
            CGRect newFrame = UIEdgeInsetsInsetRect(newSuperviewBounds,
                titleViewMargins);
            newFrame.size.height =
                self.navigationItem.titleView.frame.size.height;
            newFrame.origin.y = floor((newSuperviewBounds.size.height -
                self.navigationItem.titleView.frame.size.height) / 2);
            self.navigationItem.titleView.frame = newFrame;
        }
    }
}

Ответ 2

Вы также должны установить contentMode в UIImageView, чтобы правильно отобразить titleView в ландшафтном и/или портретном режимах:

<КОД > imgView.contentMode = UIViewContentModeScaleAspectFit;

Вся последовательность: (self является экземпляром UIViewController)

UIImageView* imgView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"myCustomTitle.png"]];
imgView.autoresizingMask=UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
imgView.contentMode=UIViewContentModeScaleAspectFit;
self.navigationItem.titleView = imgView;
[imgView release];

Ответ 3

У меня было что-то похожее, но оно возвращалось (добавлялось) в контроллер корневого представления. В конечном счете, я пошёл со следующим:

[[self navigationController] setNavigationBarHidden:YES animated:NO];
[[self navigationController] popViewControllerAnimated:YES];
[[self navigationController] setNavigationBarHidden:NO animated:NO];

И это сработало. Возможно, был лучший способ, но - после всех часов, которые я уже потратил на эту проблему, - это было достаточно для меня.

Ответ 4

Я рассмотрел эту же проблему, отслеживая начальный кадр customView, а затем переключаясь между этим и масштабированным CGRect исходного кадра в методе -setLandscape в подклассе UIButton. Я использовал подкласс UIButton как navigationItem.titleView и navigationItem.rightBarButtonItem.

В подклассе UIButton -

- (void)setLandscape:(BOOL)value
 {
     isLandscape = value;

     CGFloat navbarPortraitHeight = 44;
     CGFloat navbarLandscapeHeight = 32;

     CGRect initialFrame = // your initial frame
     CGFloat scaleFactor = floorf((navbarLandscapeHeight/navbarPortraitHeight) * 100) / 100;

     if (isLandscape) {
         self.frame = CGRectApplyAffineTransform(initialFrame, CGAffineTransformMakeScale(scaleFactor, scaleFactor));
     } else {
         self.frame = initialFrame;
     }
 }

Затем в делегатах InterfaceOrientation я вызвал метод -setLandscape в customViews, чтобы изменить их размеры.

В UIViewController -

- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration 
{
    [self updateNavbarButtonsToDeviceOrientation];;
}

- (void)updateNavbarButtonsToDeviceOrientation
 {
     ResizeButton *rightButton = (ResizeButton *)self.navigationItem.rightBarButtonItem.customView;
     ResizeButton *titleView = (ResizeButton *)self.navigationItem.titleView;

     if (self.interfaceOrientation == UIDeviceOrientationPortrait || self.interfaceOrientation == UIDeviceOrientationPortraitUpsideDown) {
         [rightButton setLandscape:NO];
         [titleView setLandscape:NO];
     } else {
         [rightButton setLandscape:YES];  
         [titleView setLandscape:YES];
     }
 }

Ответ 5

Для IOS5 и далее, поскольку это старый вопрос... Вот как я справился с той же проблемой, что и текст заголовка, не совпадающий правильно.

[[UINavigationBar appearance] setTitleVerticalPositionAdjustment:2 forBarMetrics:UIBarMetricsLandscapePhone];

Протестировано на ios5/6 sims отлично работает.

Ответ 6

Это то, что я сделал:

self.viewTitle.frame = self.navigationController.navigationBar.frame;
self.navigationItem.titleView = self.viewTitle;

ViewTitle - это представление, созданное в xib, оно принимает размер навигационной панели, и после того, как оно было добавлено, titleView отрегулирует размер, чтобы оставить место для кнопки "Назад". Похоже, что вращение работает нормально.

Ответ 7

У меня была такая же проблема, но я, похоже, обходился с последующим кодом.

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];

    UIView *urlField = self.navigationItem.leftBarButtonItem.customView;
    CGRect frame = urlField.frame;
    frame.size.width = 1000;
    urlField.frame = frame;
}

В моем случае пользовательское представление является UITextField, но я надеюсь, что это поможет вам.