iOS:狀態(tài)欄提示控件的實(shí)現(xiàn)原理
現(xiàn)在很多流行的軟件都加入了狀態(tài)欄提示的功能,比如手機(jī)qq,微信等,今天我們就一起來(lái)看看狀態(tài)欄提示控件的原理與實(shí)現(xiàn)。
一、狀態(tài)欄提示的實(shí)現(xiàn)原理
不知道大家看到狀態(tài)欄提示控件,***感覺它是怎么實(shí)現(xiàn)的呢?
我們知道即使平時(shí)寫的view是充滿全屏的,也始終不會(huì)顯示到 statusBar的上層的。也就是說(shuō)statusBar應(yīng)該是一個(gè)特殊的view,始終位于程序的topLevel,這就容易聯(lián)想到UIKit中一個(gè)特 殊的view-----UIWindow。UIWindow有一個(gè)windowLevel的屬性剛好能實(shí)現(xiàn)一直保持在上層的功能,于是方向就比較明確了。 我較早寫的兩篇博客UIWindowLevel詳解以及關(guān)于UIWindow的一點(diǎn)兒思考中對(duì)windowLevel有過(guò)詳細(xì)的介紹和驗(yàn)證。
確定了使用UIWindow來(lái)實(shí)現(xiàn)狀態(tài)欄提示控件,好像問(wèn)題就全部解決了,真的是這樣嗎?
如果你的程序僅僅支持portrait方向的話,那么最主要的功能就結(jié)束了,剩下的事情就是文本框布局和簡(jiǎn)單動(dòng)畫的實(shí)現(xiàn)了。但如果你的控件要支持其他三個(gè)方向的話,就還需要處理window的旋轉(zhuǎn)。
iOS中完整的旋轉(zhuǎn)流程如下: 設(shè)備檢測(cè)到方向旋轉(zhuǎn)->UIApplication收到旋轉(zhuǎn)事件->通知window設(shè)備發(fā)生了旋轉(zhuǎn)->window通知它的 rootViewController進(jìn)行旋轉(zhuǎn)->viewController會(huì)調(diào)整自身view的transform。觀察發(fā)現(xiàn)自始至終 window本身的位置和方向是沒有發(fā)生變化的,也就是說(shuō)如果自己創(chuàng)建一個(gè)window用于展示提示,我們需要自己處理該window的旋轉(zhuǎn),根據(jù)不同的 方向調(diào)整window的位置和transform。
綜上要實(shí)現(xiàn)狀態(tài)欄提示控件我們需要做以下兩件事:
1、創(chuàng)建一個(gè)UIWindow,指定它的frame為statusBar的frame,并且設(shè)置該window的windowLevel級(jí)別略高于statusBar的windowLevel。
2、注冊(cè)系統(tǒng)的旋轉(zhuǎn)通知,監(jiān)測(cè)設(shè)備方向變化,根據(jù)當(dāng)前設(shè)備的方向做出相應(yīng)的調(diào)整。
整個(gè)過(guò)程中主要用到了UIWindow和transfrom的知識(shí),這兩部分知識(shí)我前面寫的博客都有涉及,難點(diǎn)主要集中在自己旋轉(zhuǎn)window這一塊。
二、Window的旋轉(zhuǎn)
UIKit通過(guò)UIWindow和UIViewContoller為我們提供了一套旋轉(zhuǎn)支持的框架,在方向變化以后viewController中view的坐標(biāo)系統(tǒng)就已經(jīng)被轉(zhuǎn)到正確的方向了,我們只需要簡(jiǎn)單的重新布局就可以了。我們現(xiàn)在是直接通過(guò)UIWindow實(shí)現(xiàn)狀態(tài)欄提示控件,因此需要自己完成對(duì)該window進(jìn)行旋轉(zhuǎn)的操作。
我們知道對(duì)當(dāng)前view設(shè)置的transform是針對(duì)它的父view的,window本身就是一種特殊的view。你可能會(huì)疑問(wèn)window不就是***層的view,它還有父view嗎?
答案是YES,不信的話你可以打印一下 window的superView看看。window默認(rèn)方向是portrait方向,向下y坐標(biāo)增加,向右x坐標(biāo)增加。因此Portrait方向我們只 需要向普通的view那樣布局就可以了,其它幾個(gè)方向我們就需要用到transform和設(shè)置位置來(lái)搞定了。
下面我們看一看從Portrait方向轉(zhuǎn)到landscapeRight方向的圖示:
上圖中從左到右展示了如何將初始位置(Portrait方向),旋轉(zhuǎn)到目標(biāo)位置(landscapeRight方向)的過(guò)程。
1、原始window位置位于屏幕最上方(與statusBar的位置一樣)。
2、首先我們對(duì)這個(gè)window做順時(shí)針90°旋轉(zhuǎn),變化后到達(dá)綠色繪制位置。
3、接著我們?cè)傩薷膚indow的center到屏幕最右邊并且上下居中,達(dá)到紅色虛線所示的位置。
4、***對(duì)該window的bound進(jìn)行設(shè)置,使該window充滿屏幕最右邊的區(qū)域。注意這個(gè)時(shí)候由于window的transform已經(jīng)轉(zhuǎn)了90°,所以設(shè)置時(shí)width代表著高度,height代表這寬度。
下面是完整的處理旋轉(zhuǎn)到四個(gè)方向的代碼:
- - (void)updateOrientation:(NSNotification*)noti
- {
- UIInterfaceOrientation newOrientation = [[noti.userInfo valueForKey:UIApplicationStatusBarOrientationUserInfoKey] integerValue];
- NSLog(@"new orientation: %d", newOrientation);
- switch (newOrientation) {
- case UIInterfaceOrientationPortrait:
- {
- self.transform = CGAffineTransformIdentity;
- self.frame = CGRectMake(0, 0, SCREEN_WIDTH, HEIGHT);
- break;
- }
- case UIInterfaceOrientationPortraitUpsideDown:
- {
- // 先轉(zhuǎn)矩陣,坐標(biāo)系統(tǒng)落在屏幕有右下角,朝上是y,朝左是x
- self.transform = CGAffineTransformMakeRotation(M_PI);
- self.center = CGPointMake(SCREEN_WIDTH / 2, SCREEN_HEIGHT - HEIGHT / 2);
- self.bounds = CGRectMake(0, 0, SCREEN_WIDTH, HEIGHT);
- break;
- }
- case UIInterfaceOrientationLandscapeLeft:
- {
- self.transform = CGAffineTransformMakeRotation(-M_PI_2);
- // 這個(gè)時(shí)候坐標(biāo)軸已經(jīng)轉(zhuǎn)了90°,調(diào)整x相當(dāng)于調(diào)節(jié)豎向調(diào)節(jié),y相當(dāng)于橫向調(diào)節(jié)
- self.center = CGPointMake(HEIGHT / 2, [UIScreen mainScreen].bounds.size.height / 2);
- self.bounds = CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.height, HEIGHT);
- break;
- }
- case UIInterfaceOrientationLandscapeRight:
- {
- // 先設(shè)置transform,在設(shè)置位置和大小
- self.transform = CGAffineTransformMakeRotation(M_PI_2);
- self.center = CGPointMake(SCREEN_WIDTH - HEIGHT / 2, SCREEN_HEIGHT / 2);
- self.bounds = CGRectMake(0, 0, SCREEN_HEIGHT, HEIGHT);
- break;
- }
- default:
- break;
- }
- }
三、狀態(tài)欄提示控件源碼
講了那么多,說(shuō)好的控件呢?
下面是完整的控件代碼:
View Code SvStatusBarTipsWindow.h
- - (void)updateOrientation:(NSNotification*)noti
- {
- UIInterfaceOrientation newOrientation = [[noti.userInfo valueForKey:UIApplicationStatusBarOrientationUserInfoKey] integerValue];
- NSLog(@"new orientation: %d", newOrientation);
- switch (newOrientation) {
- case UIInterfaceOrientationPortrait:
- {
- self.transform = CGAffineTransformIdentity;
- self.frame = CGRectMake(0, 0, SCREEN_WIDTH, HEIGHT);
- break;
- }
- case UIInterfaceOrientationPortraitUpsideDown:
- {
- // 先轉(zhuǎn)矩陣,坐標(biāo)系統(tǒng)落在屏幕有右下角,朝上是y,朝左是x
- self.transform = CGAffineTransformMakeRotation(M_PI);
- self.center = CGPointMake(SCREEN_WIDTH / 2, SCREEN_HEIGHT - HEIGHT / 2);
- self.bounds = CGRectMake(0, 0, SCREEN_WIDTH, HEIGHT);
- break;
- }
- case UIInterfaceOrientationLandscapeLeft:
- {
- self.transform = CGAffineTransformMakeRotation(-M_PI_2);
- // 這個(gè)時(shí)候坐標(biāo)軸已經(jīng)轉(zhuǎn)了90°,調(diào)整x相當(dāng)于調(diào)節(jié)豎向調(diào)節(jié),y相當(dāng)于橫向調(diào)節(jié)
- self.center = CGPointMake(HEIGHT / 2, [UIScreen mainScreen].bounds.size.height / 2);
- self.bounds = CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.height, HEIGHT);
- break;
- }
- case UIInterfaceOrientationLandscapeRight:
- {
- // 先設(shè)置transform,在設(shè)置位置和大小
- self.transform = CGAffineTransformMakeRotation(M_PI_2);
- self.center = CGPointMake(SCREEN_WIDTH - HEIGHT / 2, SCREEN_HEIGHT / 2);
- self.bounds = CGRectMake(0, 0, SCREEN_HEIGHT, HEIGHT);
- break;
- }
- default:
- break;
- }
- }
- 復(fù)制代碼
- 三、狀態(tài)欄提示控件源碼
- 講了那么多,說(shuō)好的控件呢?
- 下面是完整的控件代碼:
- 復(fù)制代碼
- //
- // SvStatusBarTipsWindow.h
- // SvStatusBarTips
- //
- // Created by maple on 4/21/13.
- // Copyright (c) 2013 maple. All rights reserved.
- //
- #import <UIKit/UIKit.h>
- @interface SvStatusBarTipsWindow : UIWindow
- /*
- * @brief get the singleton tips window
- */
- + (SvStatusBarTipsWindow*)shareTipsWindow;
- /*
- * @brief show tips message on statusBar
- */
- - (void)showTips:(NSString*)tips;
- /*
- * @brief show tips message on statusBar
- */
- - (void)showTips:(NSString*)tips hideAfterDelay:(NSInteger)seconds;
- /*
- * @brief show tips icon and message on statusBar
- */
- - (void)showTipsWithImage:(UIImage*)tipsIcon message:(NSString*)message;
- /*
- * @brief show tips icon and message on statusBar
- */
- - (void)showTipsWithImage:(UIImage*)tipsIcon message:(NSString*)message hideAfterDelay:(NSInteger)seconds;
- /*
- * @brief hide tips window
- */
- - (void)hideTips;
- @end
SvStatusBarTipsWindow.m
- //
- // SvStatusBarTipsWindow.m
- // SvStatusBarTips
- //
- // Created by maple on 4/21/13.
- // Copyright (c) 2013 maple. All rights reserved.
- //
- #import "SvStatusBarTipsWindow.h"
- #define SCREEN_WIDTH ([UIScreen mainScreen].bounds.size.width)
- #define SCREEN_HEIGHT ([UIScreen mainScreen].bounds.size.height)
- #define HEIGHT 20
- #define ICON_WIDTH 20
- #define TIPMESSAGE_RIGHT_MARGIN 20
- #define ICON_RIGHT_MARGIN 5
- @interface SvStatusBarTipsWindow () {
- UILabel *_tipsLbl;
- UIImageView *_tipsIcon;
- NSTimer *_hideTimer;
- }
- @property (nonatomic, copy) NSString *tipsMessage;
- @end
- @implementation SvStatusBarTipsWindow
- @synthesize tipsMessage;
- static SvStatusBarTipsWindow *tipsWindow = nil;
- + (SvStatusBarTipsWindow*)shareTipsWindow
- {
- if (!tipsWindow) {
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- tipsWindow = [[super allocWithZone:NULL] init];
- });
- }
- return tipsWindow;
- }
- + (id)copyWithZone:(NSZone *)zone
- {
- return [[self shareTipsWindow] retain];
- }
- + (id)allocWithZone:(NSZone *)zone
- {
- return [[self shareTipsWindow] retain];
- }
- - (id)retain
- {
- return tipsWindow;
- }
- - (oneway void)release
- {
- return;
- }
- - (id)autorelease
- {
- return tipsWindow;
- }
- - (id)init
- {
- CGRect frame = [UIApplication sharedApplication].statusBarFrame;
- self = [super initWithFrame:frame];
- if (self) {
- self.autoresizingMask = UIViewAutoresizingFlexibleWidth;
- self.windowLevel = UIWindowLevelStatusBar + 10;
- self.backgroundColor = [UIColor clearColor];
- _tipsIcon = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, ICON_WIDTH, ICON_WIDTH)];
- _tipsIcon.autoresizingMask = UIViewAutoresizingFlexibleHeight;
- _tipsIcon.backgroundColor = [UIColor redColor];
- [self addSubview:_tipsIcon];
- [_tipsIcon release];
- _tipsLbl = [[UILabel alloc] initWithFrame:self.bounds];
- #ifdef NSTextAlignmentRight
- _tipsLbl.textAlignment = NSTextAlignmentLeft;
- _tipsLbl.lineBreakMode = NSLineBreakByTruncatingTail;
- #else
- _tipsLbl.textAlignment = 0; // means UITextAlignmentLeft
- _tipsLbl.lineBreakMode = 4; //UILineBreakModeTailTruncation;
- #endif
- _tipsLbl.textColor = [UIColor whiteColor];
- _tipsLbl.font = [UIFont systemFontOfSize:12];
- _tipsLbl.backgroundColor = [UIColor blackColor];
- _tipsLbl.autoresizingMask = UIViewAutoresizingFlexibleHeight;
- [self addSubview:_tipsLbl];
- [_tipsLbl release];
- [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateOrientation:) name:UIApplicationWillChangeStatusBarOrientationNotification object:nil];
- }
- return self;
- }
- - (void)dealloc
- {
- [[NSNotificationCenter defaultCenter] removeObserver:self];
- [tipsMessage release];
- [super dealloc];
- }
- #pragma mark -
- #pragma mark Notification Handle
- - (void)updateOrientation:(NSNotification*)noti
- {
- UIInterfaceOrientation newOrientation = [[noti.userInfo valueForKey:UIApplicationStatusBarOrientationUserInfoKey] integerValue];
- NSLog(@"new orientation: %d", newOrientation);
- switch (newOrientation) {
- case UIInterfaceOrientationPortrait:
- {
- self.transform = CGAffineTransformIdentity;
- self.frame = CGRectMake(0, 0, SCREEN_WIDTH, HEIGHT);
- break;
- }
- case UIInterfaceOrientationPortraitUpsideDown:
- {
- // 先轉(zhuǎn)矩陣,坐標(biāo)系統(tǒng)落在屏幕有右下角,朝上是y,朝左是x
- self.transform = CGAffineTransformMakeRotation(M_PI);
- self.center = CGPointMake(SCREEN_WIDTH / 2, SCREEN_HEIGHT - HEIGHT / 2);
- self.bounds = CGRectMake(0, 0, SCREEN_WIDTH, HEIGHT);
- break;
- }
- case UIInterfaceOrientationLandscapeLeft:
- {
- self.transform = CGAffineTransformMakeRotation(-M_PI_2);
- // 這個(gè)時(shí)候坐標(biāo)軸已經(jīng)轉(zhuǎn)了90°,調(diào)整x相當(dāng)于調(diào)節(jié)豎向調(diào)節(jié),y相當(dāng)于橫向調(diào)節(jié)
- self.center = CGPointMake(HEIGHT / 2, [UIScreen mainScreen].bounds.size.height / 2);
- self.bounds = CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.height, HEIGHT);
- break;
- }
- case UIInterfaceOrientationLandscapeRight:
- {
- // 先設(shè)置transform,在設(shè)置位置和大小
- self.transform = CGAffineTransformMakeRotation(M_PI_2);
- self.center = CGPointMake(SCREEN_WIDTH - HEIGHT / 2, SCREEN_HEIGHT / 2);
- self.bounds = CGRectMake(0, 0, SCREEN_HEIGHT, HEIGHT);
- break;
- }
- default:
- break;
- }
- }
- #pragma mark -
- #pragma mark Tips Method
- /*
- * @brief show tips message on statusBar
- */
- - (void)showTips:(NSString*)tips
- {
- if (_hideTimer) {
- [_hideTimer invalidate];
- [_hideTimer release];
- }
- _tipsIcon.image = nil;
- _tipsIcon.hidden = YES;
- CGSize size = [tips sizeWithFont:_tipsLbl.font constrainedToSize:CGSizeMake(320, 30)];
- size.width += TIPMESSAGE_RIGHT_MARGIN;
- if (size.width > self.bounds.size.width - ICON_WIDTH) {
- size.width = self.bounds.size.width - ICON_WIDTH;
- }
- _tipsLbl.frame = CGRectMake(self.bounds.size.width - size.width, 0, size.width, self.bounds.size.height);
- _tipsLbl.text = tips;
- [self makeKeyAndVisible];
- }
- - (void)showTips:(NSString*)tips hideAfterDelay:(NSInteger)seconds
- {
- [self showTips:tips];
- _hideTimer = [NSTimer scheduledTimerWithTimeInterval:seconds target:self selector:@selector(hideTips) userInfo:nil repeats:NO];
- }
- /*
- * @brief show tips icon and message on statusBar
- */
- - (void)showTipsWithImage:(UIImage*)tipsIconImage message:(NSString*)message
- {
- if (_hideTimer) {
- [_hideTimer invalidate];
- [_hideTimer release];
- }
- CGSize size = [message sizeWithFont:_tipsLbl.font constrainedToSize:self.bounds.size];
- size.width += TIPMESSAGE_RIGHT_MARGIN;
- if (size.width > self.bounds.size.width - ICON_WIDTH) {
- size.width = self.bounds.size.width - ICON_WIDTH;
- }
- _tipsLbl.frame = CGRectMake(self.bounds.size.width - size.width, 0, size.width, self.bounds.size.height);
- _tipsLbl.text = message;
- _tipsIcon.center = CGPointMake(self.bounds.size.width - _tipsLbl.bounds.size.width - ICON_WIDTH / 2 - ICON_RIGHT_MARGIN, self.bounds.size.height / 2);
- _tipsIcon.image = tipsIconImage;
- _tipsIcon.hidden = NO;
- [self makeKeyAndVisible];
- }
- - (void)showTipsWithImage:(UIImage*)tipsIconImage message:(NSString*)message hideAfterDelay:(NSInteger)seconds
- {
- [self showTipsWithImage:tipsIconImage message:message];
- _hideTimer = [NSTimer scheduledTimerWithTimeInterval:seconds target:self selector:@selector(hideTips) userInfo:nil repeats:NO];
- }
- /*
- * @brief hide tips window
- */
- - (void)hideTips
- {
- self.hidden = YES;
- }
- @end
該狀態(tài)欄提示控件實(shí)現(xiàn)了添加提示圖標(biāo)和提示文字,以及自動(dòng)隱藏等功能。顯示和隱藏動(dòng)畫實(shí) 現(xiàn)起來(lái)比較簡(jiǎn)單,控件中就沒有實(shí)現(xiàn),大家可以根據(jù)需要隨意發(fā)揮。該控件使用單例模式,接口非常簡(jiǎn)單,使用起來(lái)很方便。上面代碼相信大家都能看得懂,這里就不展開講了,有什么問(wèn)題歡迎討論。