在iPhone上向状态栏添加视图

62

能否在大小为(320 x 20)的状态栏上添加一个UIView?我不想隐藏状态栏,只想将其添加到状态栏顶部。


4
如果你有意向在苹果应用商店发布,请注意以下事项:Reeder 应用因为这个功能被驳回。 - phi
1
@Irene - 我为我的Python for iOS应用编写了一个自定义状态栏视图,最近更新已经通过App Store审核。不过,自Reeder应用被拒绝以来,苹果的政策可能已经发生了变化。 - chown
Appstore里有很多具备这种功能的应用程序。 - Sergey Kopanev
5个回答

87

你可以通过创建一个自己的窗口放在现有状态栏之上来轻松实现这一点。

只需创建一个简单的UIWindow子类,并使用以下initWithFrame:重写:

@interface ACStatusBarOverlayWindow : UIWindow {
}
@end

@implementation ACStatusBarOverlayWindow
- (id)initWithFrame:(CGRect)frame {
    if ((self = [super initWithFrame:frame])) {
        // Place the window on the correct level and position
        self.windowLevel = UIWindowLevelStatusBar+1.0f;
        self.frame = [[UIApplication sharedApplication] statusBarFrame];

        // Create an image view with an image to make it look like a status bar.
        UIImageView *backgroundImageView = [[UIImageView alloc] initWithFrame:self.frame];
        backgroundImageView.image = [UIImage imageNamed:@"statusBarBackground.png"];
        [self addSubview:backgroundImageView];
        [backgroundImageView release];

        // TODO: Insert subviews (labels, imageViews, etc...)
    }
    return self;
}
@end

现在你可以在应用程序的视图控制器中创建一个新类的实例并使其可见。

overlayWindow = [[ACStatusBarOverlayWindow alloc] initWithFrame:CGRectZero];
overlayWindow.hidden = NO;

要注意不要通过使用- (void)makeKeyAndVisible或类似的方法来更改窗口键状态。如果使您的主窗口(应用程序代理中的UIWindow)失去关键状态,则在轻击状态栏等操作时,您将遇到滚动滚动视图以返回顶部的问题。

请注意,不要使用- (void)makeKeyAndVisible或类似的方法来更改窗口键状态。如果使您的主窗口(即应用程序委托中的UIWindow)失去关键状态,那么在轻触状态栏等情况下,滚动滚动视图返回顶部可能会出现问题。


1
请注意,在常规状态栏(灰色状态栏)和不透明黑色状态栏上放置覆盖层是很简单的。如果您的应用程序具有半透明状态栏,则需要隐藏它并在其位置放置自己的视图(因为将半透明状态栏放在原始状态栏上只会弄乱界面;) - alleus
4
就我所知,我在显示窗口方面遇到了困难,原因是没有保留实例。在ARC环境下,应用程序似乎非常热衷于释放这些实例,因此如果您不将其明确保留为实例变量/属性,则它会在显示之前被释放。 - Chris Wagner
1
@MaciejHepner 我必须保留UIWindow,如果没有任何人保留它,那么它将被释放并且永远不会显示出来。实际上,我最终使对象创建一个保留循环以保留自身,然后在我的解散中,我打破了保留循环,以便它被释放。 - Chris Wagner
这个解决方案非常出色,直到我看到在iPad和屏幕旋转时发生的情况 - 出现了严重的故障 :/ - Heps
这对我有用,但我想知道如何在某些ViewControllers上拥有它,而不是像添加时那样全部都有。我能够在ViewDidDisappear上隐藏它,但这意味着当我在应该拥有它的VC之间切换时,它会闪烁为隐藏状态,然后再次显示。 - Chucky
显示剩余6条评论

58

我编写了一个静态库,类似于Reeders状态栏覆盖层,您可以在这里找到它:https://github.com/myell0w/MTStatusBarOverlay

MTStatusBarOverlay MTStatusBarOverlay

目前它支持iPhone和iPad、默认和不透明黑色状态栏风格、旋转、3种不同的动画模式、历史跟踪和更多好东西!

欢迎使用它或向我发送Pull请求以增强它!


如果我的应用程序中没有状态栏,我该如何使用它? - Praveen S
5
坏消息,大家注意了,苹果公司开始拒绝使用MTStatusBarOverlay或类似解决方案的应用程序,这些解决方案“在本机状态栏上方显示自定义状态栏覆盖层”。显然,这违反了HIG规定。 :( - Błażej
1
很遗憾,确实有一些使用MTStatusBarOverlay的应用被拒绝了,但是也有一些没有。我经常收到来自使用MTStatusBarOverlay的人的信息,他们的应用没有任何(审核)问题,所以你仍然可以尝试一下 :-) - myell0w
我认为在应用程序中使用MTStatusBarOverlay是可以的。Tweetbot 3广泛使用这种UI。(向您显示推文发送进度。) - Hlung

4

所有的答案看起来都可以运行,但在iOS6.0中我遇到了以下问题:

1/ 旋转效果不佳

2/ 窗口(状态栏就像窗口一样)需要rootViewController

我使用了myell0w的答案,但旋转效果不太好。我只是删除了一个额外的窗口,并使用AppDelegate中的UIWindow来实现状态栏。也许这种解决方案只适用于一个UIViewController-app...

我是这样实现的:

1/ 在ApplicationDelegate中:

self.window.windowLevel = UIWindowLevelStatusBar + 1;
self.window.backgroundColor = [UIColor clearColor];
self.window.rootViewController = _journalController;

2/ 创建自定义UIView并在其中实现所有需要的内容: 例如可触摸状态栏:

@interface LoadingStatusBar : UIControl 

轻松创建并添加到您的控制器视图中:

_loadingBar = [[LoadingStatusBar alloc] initWithFrame:topFrame];
[self addSubview:_loadingBar];

3/ 在 initWithFrame: 中添加控制器视图时需要注意一些技巧。

    CGRect mainFrame = self.bounds;
    mainFrame.origin.y = 20;
    self.bounds = mainFrame;

您的控制器视图将有两个视图 - 内容视图和状态栏视图。您可以在需要时显示状态栏,也可以隐藏它。 内容视图的框架将为:

_contentView.frame = CGRectMake(0, 20, self.bounds.size.width, self.bounds.size.height);

4/ 最后一个神奇的技巧 :) 为了检测非可触摸区域的触摸,我使用了:

-(id)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    if (point.y < 20) return _loadingBar;
    return [super hitTest:point withEvent:event];
}

目前它在iPad/iPhone上运行良好,并且支持iOS 4到6。


3

仅是为了驳斥"您无法做到这一点的评论"...

我不知道怎么做,但我知道它是可行的。名为Reeder的Feed阅读器应用程序可以实现。

从屏幕右上角的小圆点可以看出,Reeder会在屏幕右上角放置一个小圆点。当您点击它时,该条将填满整个状态栏,直到再次点击它使其变小。

屏幕右上角的小图标 alt text


1
首先,非常感谢@Martin Alléus提供了这个实现的代码。
我发帖是为了分享我遇到的问题和解决方案,因为我相信其他人可能也会遇到同样的问题。
如果应用程序在通话进行中启动,则状态栏高度将为40像素,这意味着自定义状态栏将使用该高度进行初始化。 但是,如果通话在您仍然在应用程序中时结束,则状态栏高度仍将保持在40像素,并且看起来很奇怪。
所以解决方案很简单:我使用了通知中心来订阅应用程序的状态栏框架更改委托并调整框架:
- (void)application:(UIApplication *)application didChangeStatusBarFrame:(CGRect)oldStatusBarFrame {
    //an in call toggle was done    
    //fire notification
    [[NSNotificationCenter defaultCenter] postNotificationName:kStatusBarChangedNotification object:[NSValue valueWithCGRect:oldStatusBarFrame]];
}

在ACStatusBarOverlayWindow中,我们订阅通知:
-(id)initWithFrame:(CGRect)frame
{
    if ((self = [super initWithFrame:frame]))
    {
        // Place the window on the correct level & position
        self.windowLevel = UIWindowLevelStatusBar + 1.0f;
        self.frame = [UIApplication sharedApplication].statusBarFrame;
        self.backgroundColor = [UIColor blackColor];

        //add notification observer for in call status bar toggling
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(statusBarChanged:) name:kStatusBarChangedNotification object:nil];
    }
    return self;
}

以及我们的代码来调整框架:

- (void)statusBarChanged:(NSNotification*)notification {
    //adjust frame...    
    self.frame = [UIApplication sharedApplication].statusBarFrame;
    //you should adjust also the other controls you added here
}

kStatusBarChangedNotification只是我为了方便引用而使用的一个常量,你可以简单地将其替换为字符串,或者全局声明该常量。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接