• custom iPhone statusbar


    最近项目组里要求实现在iPhone的状态栏上显示自定义的图标,但是公开的书籍和论坛都没有介绍。看了一篇博客介绍了如此如何进行修改,转载如下:

    用过Reeder的应该都会发现,在进行同步时,右上角会出现一个自定义的图标。而在点击它时,就会向左扩张覆盖住原状态栏,并显示同步状态。
    这个设计非常巧妙,因为传统的设计在显示状态时,往往会占用掉几十像素;而在阅读时,用户非常希望主要内容能占据更多的空间。
    那么这个设计是怎么实现的呢?下面就来模拟一下。

    首先要说明的是,Apple并没有开放状态栏的API,所以想要改动它的话,就只能用私有API了:

    @interface UIApplication ()
    -(void)addStatusBarImageNamed:(id)named removeOnExit:(BOOL)exit;
    -(void)addStatusBarImageNamed:(id)named;
    -(void)removeStatusBarImageNamed:(id)named;
    @end

    这里的named实际上是个NSString对象,指向/System/Library/CoreServices/SpringBoard.app中的图片,例如@"Airplain"、@"Play"等。如果你越狱了的话,可能会在这个文件夹里找到一些越狱应用的图标。

    [[UIApplication sharedApplication] addStatusBarImageNamed:@"Play"];

    要注意的是进入后台时,并不会自动删除图标,即使设置removeOnExit为YES也无效。所以最好的方法是自己调用removeStatusBarImageNamed:,它只能删除通过addStatusBarImageNamed:添加的图标,而不能删除系统自己显示的图标。
    还要说明的是,模拟器上也是无效的。

    这种方法虽然简单,但因为用到了私有API,你的应用很可能无法在App Store上架。
    此外,可用的图标有限,而你又没权限在SpringBoard.app中添加图标。
    更重要的是这个图标只能加在电池图标的左边,并且无法响应触摸事件。

    既然缺点那么多,我们还是另寻他法吧。
    事实上在Apple的View Programming Guide for iOS文档里就有提到更改Window Level这个技巧。
    于是看看Window Level的可用值:

    typedef CGFloat UIWindowLevel;
    const UIWindowLevel UIWindowLevelNormal; // 0.0
    const UIWindowLevel UIWindowLevelAlert; // 2000.0
    const UIWindowLevel UIWindowLevelStatusBar; // 1000.0

    现在知道为什么系统弹出来的对话框肯定会比你的app高了吧,因为它的Window Level更高,你的view和layer和它根本不是一个级别的。
    不过我并不是来搞对话框的,只要能覆盖状态栏就行了。于是解决办法就很简单了:创建一个UIWindow对象,将它的windowLevel设得比UIWindowLevelStatusBar更高,然后用它来模拟状态栏。

    接下来就做个demo测试一下。要实现的效果也很简单,用自定义的图标挡住电池图标;点击这个图标就向左移动,覆盖整个状态栏,并用文本显示点击次数;再次点击就向右移动,只显示那个图标。主窗口也实现一个按钮,用于切换显示和隐藏自定义状态栏。

    于是先实现一个StatusBarWindow类:

    View Code
    #import <Foundation/Foundation.h>
    
    @interface StatusBarWindow : UIWindow {
        UIImageView *iconImage;
        UILabel *textLabel;
        NSUInteger clickCount;
    }
    
    @property (nonatomic, assign) UIImageView *iconImage;
    @property (nonatomic, assign) UILabel *textLabel;
    @property (nonatomic) NSUInteger clickCount;
    
    + (StatusBarWindow *)newStatusBarWindow;
    
    @end
    
    
    @implementation StatusBarWindow
    
    @synthesize iconImage, textLabel, clickCount;
    
    static const CGRect windowFrame0 = {{296, 0}, {320, 20}};
    static const CGRect windowFrame1 = {{0, 0}, {320, 20}};
    static const CGRect imageFrame0 = {{6, 1}, {16, 16}};
    static const CGRect imageFrame1 = {{2, 1}, {16, 16}};
    static const CGRect textFrame = {{60, 0}, {200, 20}};
    
    + (StatusBarWindow *)newStatusBarWindow {
        StatusBarWindow *statusBar = [[StatusBarWindow alloc] initWithFrame:windowFrame0];
        if (statusBar) {
            statusBar.windowLevel = UIWindowLevelStatusBar + 1;
            
            UIImageView *background = [[UIImageView alloc] initWithFrame:windowFrame1];
            background.image = [UIImage imageNamed:@"background.png"];
            [statusBar addSubview:background];
            [background release];
            
            UIImageView *icon = [[UIImageView alloc] initWithFrame:imageFrame0];
            icon.image = [UIImage imageNamed:@"clock.png"];
            statusBar.iconImage = icon;
            [statusBar addSubview:icon];
            [icon release];
            
            UILabel *text = [[UILabel alloc] initWithFrame:textFrame];
            text.backgroundColor = [UIColor clearColor];
            text.textAlignment = UITextAlignmentCenter;
            statusBar.textLabel = text;
            [statusBar addSubview:text];
            [text release];
        }
        
        return statusBar;
    }
    
    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
        [UIView beginAnimations:nil context:nil];
        [UIView setAnimationDuration:1];
        textLabel.text = [NSString stringWithFormat:@"已点击%d次状态栏", ++clickCount];
        if (clickCount % 2) {
            self.frame = windowFrame1;
            iconImage.frame = imageFrame1;
        } else {
            self.frame = windowFrame0;
            iconImage.frame = imageFrame0;
        }
        [UIView commitAnimations];
    }
    
    @end

    这里的难点主要是坐标的计算。需要知道的是状态栏高20 points,长320 points,电池图标约占用了右侧24 points的宽度。
    我找了张时钟的图片,然后缩小到了16像素(高清为32像素)。图片不能太大,上下左右都得留2 points左右的空间,否则就显得很挤。
    背景图片很容易搞定,截图后取没有图标的部分,然后拉伸到320像素(高清为640像素)长即可。

    现在用newStatusBarWindow创建一个StatusBarWindow对象,你会发现什么效果都没有。原来UIWindow默认就是隐藏的,需要把hidden属性设为NO才能显示。
    于是继续实现controller:

    View Code
    #import <UIKit/UIKit.h>
    #import "StatusBarWindow.h"
    
    @interface StatusBarDemoViewController : UIViewController {
        StatusBarWindow *statusBar;
        UIButton *switchButton;
        BOOL showStatusBar;
    }
    
    @property (nonatomic, retain) StatusBarWindow *statusBar;
    @property (nonatomic, retain) UIButton *switchButton;
    @property (nonatomic) BOOL showStatusBar;
    
    - (IBAction)click;
    
    @end
    
    
    @implementation StatusBarDemoViewController
    
    @synthesize statusBar, switchButton, showStatusBar;
    
    - (IBAction)click {
        showStatusBar = !showStatusBar;
        if (showStatusBar) {
            statusBar.hidden = NO;
            [switchButton setTitle: @"隐藏自定义状态栏" forState:UIControlStateNormal];
        } else {
            statusBar.hidden = YES;
            [switchButton setTitle: @"显示自定义状态栏" forState:UIControlStateNormal];
        }
    }
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        StatusBarWindow *statusBarWindow = [StatusBarWindow newStatusBarWindow];
        self.statusBar = statusBarWindow;
        [statusBarWindow release];
        
        CGRect frame = {{80, 200}, {160, 30}};
        UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
        button.frame = frame;
        [button addTarget:self action:@selector(click) forControlEvents:UIControlEventTouchUpInside];
        [button setTitle:@"显示自定义状态栏" forState:UIControlStateNormal];
        self.switchButton = button;
        [self.view addSubview:button];
    }
    
    - (void)viewDidUnload {
        self.statusBar = nil;
        self.switchButton = nil;
    }
    
    - (void)dealloc {
        [super dealloc];
        [statusBar release];
        [switchButton release];
    }
    
    @end

    这边就更简单了,只是创建StatusBarWindow对象,然后切换它的hidden属性而已。

    OK搞定,不知道你有没有发现这种方式的缺点呢?
    很显然,自定义的状态栏必须是不透明的,否则无法挡住原生状态栏。这也就意味着你不能在应用中使用半透明样式的原生状态栏,否则看上去就很不协调了。
    另一个缺点就是触摸事件不能传递给原生状态栏了,所以滚动到顶部的效果就得自己处理了,而越狱后关于状态栏的自定义手势也失效了。

  • 相关阅读:
    uniapp判断token多次登录问题
    vue强制刷新子组件到初始状态
    时间戳转化时间过滤器
    axios二次封装具有请求/响应拦截的http请求
    vue常见的工具函数
    解决npm i 初始化,core-js报错
    node环境变量配置
    scss基本使用
    Vue element-ui父组件控制子组件的表单校验
    antd-Calendar(日历)自动嵌入对应时间问题
  • 原文地址:https://www.cnblogs.com/xuanyuanchen/p/2582564.html
Copyright © 2020-2023  润新知