Обработка строки состояния In-Call с пользовательской модальной презентацией

Эта проблема

Я заметил странное поведение при представлении UINavigationController (с контроллером корневого представления, уже нажал, естественно) с UIViewControllerAnimatedTransitioning во время телефонного звонка.

  • Если строка состояния вызова включена после представления навигационного контроллера, навигационный контроллер смещает свой вид вниз, как и ожидалось. Но когда вызов завершен, контроллер не сдвигает свой взгляд назад, оставляя пробел в 20 пунктов под строкой состояния.
  • Если строка состояния во время разговора включена до представления контроллера, контроллер вообще не учитывает строку состояния, оставляя 4p навигационной панели высотой 44p, выглядывающей из-под строки состояния 40p. Когда вызов завершен, контроллер смещает свой обзор вниз, чтобы приспособить обычную строку состояния 20p.

* примечание: это было протестировано на симуляторе из-за легкости включения / выключения строки состояния в вызове, но тестеры наблюдали это явление на реальных телефонах.

Мой (Частичный) Обходной путь

Я взломал проблему, отрегулировав рамку контроллера во время презентации, если строка состояния была ненормальной высотой:

@interface CustomAnimationController : NSObject <UIViewControllerAnimatedTransitioning>
@end

@implementation CustomAnimationController

- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
    UIViewController *toController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    UIView *container = [transitionContext containerView];

    CGRect frame = [transitionContext finalFrameForViewController:toController];
    if (CGRectEqualToRect(frame, CGRectZero))
    {
        // In my experience, the final frame is always a zero rect, so this is always hit
        UIEdgeInsets insets = UIEdgeInsetsZero;
        // My "solution" was to inset the container frame by the difference between the 
        // actual status bar height and the normal status bar height
        insets.top = CGRectGetHeight([UIApplication sharedApplication].statusBarFrame) - 20;
        frame = UIEdgeInsetsInsetRect(container.bounds, insets);
    }

    toController.view.frame = frame;
    [container addSubview:toController.view];

    // Perform whiz-bang animation here
}    

@end

Это решение гарантирует, что панель навигации находится ниже строки состояния, но навигационному контроллеру по-прежнему не удается переключиться обратно вверх, когда вызов завершен. Таким образом, приложение, по крайней мере, пригодно для использования, но после завершения вызова над панелью навигации появляется ужасный пробел в 20 пунктов.

Есть ли способ лучше?

Я пропустил какой-то важный шаг, чтобы убедиться, что контроллер навигации самостоятельно учитывает строку состояния в вызове? Это работает просто отлично, когда представлено со встроенным модальным стилем представления.

На мой взгляд, это похоже на ошибку UIKit - в конце концов, контроллер навигации, похоже, получает UIApplicationWillChangeStatusBarFrameNotification (см. второй пункт "Проблемы"). Если кто-то еще сталкивался с этой проблемой и нашел лучший способ, я был бы очень признателен за решение.

5 ответов

Я потратил слишком много времени на преодоление проблемы с высотой строки состояния и нашел общее решение, которое работает для меня, и я думаю, что оно подойдет и для вашей ситуации.

Во-первых, пара вещей, которые странны в строке состояния.

  1. Это обычно 20 очков и экран обычно 568 очков

  2. Во время разговора строка состояния имеет высоту 40 баллов, а экран высотой 548 баллов.

  3. Пока строка состояния скрыта, строка состояния равна 0 баллам, а экран - 568 баллов.

Если строка состояния изменяется, но вы не обновляете высоту экрана, тогда вычисления будут отключены, и это можно увидеть в некоторых довольно громких (и даже по умолчанию) приложениях.

Итак, решение, которое я придумал, состоит из двух частей: 1. Создайте макрос, чтобы получить настроенную высоту экрана. 2. Зарегистрируйте уведомление, чтобы обновить представление при изменении строки состояния.

Вот макросы, я бы порекомендовал поместить их в свой prefix файл

#define kScreenWidth     [UIScreen mainScreen].bounds.size.width
#define kStatusBarHeight (([[UIApplication sharedApplication] statusBarFrame].size.height == 20.0f) ? 20.0f : (([[UIApplication sharedApplication] statusBarFrame].size.height == 40.0f) ? 20.0f : 0.0f))
#define kScreenHeight    (([[UIApplication sharedApplication] statusBarFrame].size.height > 20.0f) ? [UIScreen mainScreen].bounds.size.height - 20.0f : [UIScreen mainScreen].bounds.size.height)

Кроме того, вот вызов центра уведомлений, который я нашел, работает для меня 100% времени, когда изменяется строка состояния.

NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self.view selector:@selector(layoutSubviews) name:UIApplicationWillChangeStatusBarFrameNotification object:nil];

У меня была та же самая проблема, и проблема была с представлением, которое я представлял, который был автоматически настроен на 20 пунктов из-за полосы в вызове. Однако, поскольку я вставляю представление в контейнер, мне пришлось сбросить фрейм, чтобы он соответствовал границам контейнера.

- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
    UIView* destinationView = [transitionContext viewForKey:UITransitionContextToViewKey];
    UIView* container = transitionContext.containerView;

    // Make sure destination view matches container bounds
    // This is necessary to fix the issue with in-call bar
    // which adjusts the view's frame by 20pt.
    destinationView.frame = container.bounds;

    // Add destination view to container
    [container insertSubview:destinationView atIndex:0];

    // [reducted]
}

Вот обходной путь, который я добавил в свой делегат приложения, который исправил проблему для меня:

- (void)application:(UIApplication *)application willChangeStatusBarFrame:(CGRect)newStatusBarFrame
{
    if (newStatusBarFrame.size.height < 40) {
        for (UIView *view in self.window.subviews) {
            view.frame = self.window.bounds;
        }
    }
}

У меня была та же проблема, и я исправил ее с помощью просмотра кадра обновления вызовов, поэтому я получил строку состояния высоты. Моя проблема была решена.

 UIView *toViewSnapshot = [toView resizableSnapshotViewFromRect:toView.frame afterScreenUpdates:YES withCapInsets:UIEdgeInsetsZero];

Просто установите фрейм в layoutSubviews так, как он вызывается при изменении высоты строки состояния. Как правило, все настройки фрейма выполняются только в layoutSubviews.

Другие вопросы по тегам