博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
iPhone 添加状态栏图标及自定义状态栏
阅读量:4110 次
发布时间:2019-05-25

本文共 5568 字,大约阅读时间需要 18 分钟。

用过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的文档里就有提到更改Window Level这个技巧。
于是看看Window Level的可用值:
typedef CGFloat UIWindowLevel;const UIWindowLevel UIWindowLevelNormal; // 0.0const UIWindowLevel UIWindowLevelAlert; // 2000.0const UIWindowLevel UIWindowLevelStatusBar; // 1000.0
现在知道为什么系统弹出来的对话框肯定会比你的app高了吧,因为它的Window Level更高,你的view和layer和它根本不是一个级别的。
不过我并不是来搞对话框的,只要能覆盖状态栏就行了。于是解决办法就很简单了:创建一个UIWindow对象,将它的windowLevel设得比UIWindowLevelStatusBar更高,然后用它来模拟状态栏。
接下来就做个demo测试一下。要实现的效果也很简单,用自定义的图标挡住电池图标;点击这个图标就向左移动,覆盖整个状态栏,并用文本显示点击次数;再次点击就向右移动,只显示那个图标。主窗口也实现一个按钮,用于切换显示和隐藏自定义状态栏。
于是先实现一个StatusBarWindow类:
#import 
@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:
#import 
#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搞定,不知道你有没有发现这种方式的缺点呢?
很显然,自定义的状态栏必须是不透明的,否则无法挡住原生状态栏。这也就意味着你不能在应用中使用半透明样式的原生状态栏,否则看上去就很不协调了。
另一个缺点就是触摸事件不能传递给原生状态栏了,所以滚动到顶部的效果就得自己处理了,而越狱后关于状态栏的自定义手势也失效了。

转载地址:http://cnosi.baihongyu.com/

你可能感兴趣的文章
Java NIO详解
查看>>
在JS中 onclick="save();return false;"return false是
查看>>
VS编译器运行后闪退,处理方法
查看>>
idea 有时提示找不到类或者符号
查看>>
matplotlib.pyplot.plot()参数详解
查看>>
拉格朗日对偶问题详解
查看>>
MFC矩阵运算
查看>>
ubuntu 安装mysql
查看>>
c# 计算器
查看>>
C# 简单的矩阵运算
查看>>
gcc 常用选项详解
查看>>
c++输出文件流ofstream用法详解
查看>>
firewalld的基本使用
查看>>
Linux下SVN客户端使用教程
查看>>
Linux分区方案
查看>>
nc 命令详解
查看>>
如何使用 systemd 中的定时器
查看>>
git命令速查表
查看>>
linux进程监控和自动重启的简单实现
查看>>
OpenFeign学习(三):OpenFeign配置生成代理对象
查看>>