移動APP現在發展的如火如荼,各大應用商店都湧現了一大批優秀的app產品,但是作為一名app的消費者,以及app開發工程師,我覺得今天有必要在這裡和大家一起來探討一下如何實現一個簡單的app開發過程,或者說一個app的結構該大致怎麼實現。
在市面上,我們所使用的大部分工具應用類型的app都是有一定的界面結構的(類似淘寶,QQ, 微信),其中最主要的界面結構歸納起來就是使用 “導航欄(navigationBar) + 主視圖(mainView)+工具欄(tabBar)”來實現,如圖所示:
今天,就來講一下,如何實現一個簡單的應用型app最主要的界面UI框架。在這裡我把這個框架拆分為幾個部分,這樣既有利於大家的理解也體現低耦合的特性(因為本身這幾個自定義控件都是獨立的,交互都通過接口來實現)。
如上圖所示,這個界面包含了NavigationBar , UIViewController, 以及tabBarController;導航欄navigationbar顧名思義,當然是為了滿足用戶跳轉與返回的操作;UIViewController的作用即是用於呈現給用戶所需要看的內容;tabBarController的作用是用於管理多個控制器,輕松的完成視圖之間的切換。
我們來簡單的了解一下它的view層級圖:
第一層是我自定義的一個導航欄CustomNavigationController繼承自UINavigationController;我們通常把它作為整個程序的rootViewController,在AppDelegate中的applicationDidFinishLaunching函數中設置為根視圖。
第二層視圖CustomTabBarController(繼承自UIViewController)是在CustomNavigationController初始化時,通過初始化API函數initWithRootViewController附加在導航欄上的,其作用是添加tabBar控件以及其他的UiviewController用於顯示。
第三層視圖CustomTabBar,繼承自UIView; 是我們自定義的UITabBar控件,上面顯示的每一個tabItem都對應著一個viewController;tabItem是自定義的按鈕繼承自UIButton,在下面的講解中,我會主意介紹這些控件該如何實現。
創建自定義的CustomTabBarController
1.設置CustomTabBar控件的frame大小以及顯示內容頁面的frame大小
CustomTabBarController中聲明了兩個變量,一個是CustomTabBar對象(自定義的UITabBar),另一個是UIView對象,在界面進入viewWillAppear的時候初始化這個
兩個控件,代碼如下:
- (void)setTabBarHidden:(BOOL)hidden animated:(BOOL)animated{ _tabBarHidden = hidden; __weak CustomTabBarController *weakSelf = self; void (^block)() = ^{ CGSize viewSize = weakSelf.view.frame.size; CGFloat tabBarStartingY = viewSize.height; CGFloat contentViewHeight = viewSize.height; CGFloat tabBarHeight = CGRectGetHeight([[weakSelf tabBar] frame]); if (!tabBarHeight) { tabBarHeight = 55; } if (![weakSelf parentViewController]) { if (UIInterfaceOrientationIsLandscape([weakSelf interfaceOrientation])) { viewSize = CGSizeMake(viewSize.height, viewSize.width); } } if (!hidden) { tabBarStartingY = viewSize.height - tabBarHeight; // if (![[weakSelf tabBar] isTranslucent]) { contentViewHeight -= ([[weakSelf tabBar] minimumContentHeight] ?: tabBarHeight); // } [[weakSelf tabBar] setHidden:NO]; } [[weakSelf tabBar] setFrame:CGRectMake(0, tabBarStartingY, viewSize.width, tabBarHeight)]; [[weakSelf contentView] setFrame:CGRectMake(0, 0, viewSize.width, contentViewHeight)]; }; void (^completion)(BOOL) = ^(BOOL finished){ if (hidden) { [[weakSelf tabBar] setHidden:YES]; } }; if (animated) { [UIView animateWithDuration:0.24 animations:block completion:completion]; } else { block(); completion(YES); } }
2.設置每個tabBarItem對應的UIViewController
在XCode工程中我新建了四個UIViewController對象,分別是FirstViewController, SecondViewController, ThirdViewController, 以及FourthViewController。這四個視圖對象想分別與四個tabBarItem對應,我們調用函數setShowViewController來實現,代碼如下:
- (void)setShowViewControllers:(NSMutableArray *)mviewControllers{ self.viewControllers = mviewControllers; if (mviewControllers && [mviewControllers isKindOfClass:[NSArray class]]) { self.viewControllers = mviewControllers; NSMutableArray *tabBarItems = [[NSMutableArray alloc] init]; for (UIViewController *viewController in mviewControllers) { CustomTabBarItem *tabBarItem = [[CustomTabBarItem alloc] init]; [tabBarItem setTitle:viewController.title forState:UIControlStateNormal]; [tabBarItems addObject:tabBarItem]; } [self.tabBar setTabBarItems:tabBarItems]; } else { // for (UIViewController *viewController in _viewControllers) { // [viewController Custom_setTabBarController:nil]; // } self.viewControllers = nil; } }
3.設置當前要顯示的頁面
承接上面的功能,既然我們的tabBarItem每一個都對應一個UIViewController,那如何實現讓每一次的點擊按鈕過後,我們的界面就能跳轉顯示為正確的呢,設置的代碼如下:
//設置當前顯示的頁面 - (void)setContentViewIndex:(NSInteger)index{ self.selectedIndex = index; if(index >= self.viewControllers.count){ return; } if([self selectedViewController]){ [[self selectedViewController] willMoveToParentViewController:nil]; [[[self selectedViewController] view] removeFromSuperview]; [[self selectedViewController] removeFromParentViewController]; } [[self tabBar] setSelectedItem:[[self tabBar] items][self.selectedIndex]]; [self setSelectedViewController:[[self viewControllers] objectAtIndex:self.selectedIndex]]; [self addChildViewController:[self selectedViewController]]; [[[self selectedViewController] view] setFrame:[[self contentView] bounds]]; [[self contentView] addSubview:[[self selectedViewController] view]]; [[self selectedViewController] didMoveToParentViewController:self]; }
創建自定義的CustomTabBar
TabBar在app中可謂是個非常重要的常客,為什麼說他重要呢,因為它相當於是打開一個app裡面所有功能的鑰匙;tabBar中的每一個tabBarItem都對應
一個viewController, 通過觸發按鈕事件我們可以切換不同的頁面。
1.設置tabBarItems
tabBar可不能沒有tabBarItem, 通過頭文件中提供的接口setTabBarItems,可以將app所需要的tabBarItem對象設置好,代碼如下:
- (void)setTabBarItems:(NSArray *)m_array{ for (CustomTabBarItem *item in m_array) { [item removeFromSuperview]; } self.items = m_array; for (CustomTabBarItem *item in m_array) { NSLog(@"%@", item); [item addTarget:self action:@selector(tabBarItemWasSelected:) forControlEvents:UIControlEventTouchDown]; [self addSubview:item]; } }
2.設置界面切換代理
我們將顯示當前所需界面的函數寫在了CustomTabBarController這個類中,而我們的點擊事件則是在CustomTabBar中,那如何才能調用到設置當前頁面的函數呢!
我們這邊就采用了代理delegate的模式,代碼如下:
@protocol CustomTabBarDelegate- (BOOL)tabBar:(CustomTabBar *)tabBar shouldSelectItemAtIndex:(NSInteger)index; - (void)tabBar:(CustomTabBar *)tabBar didSelectItemAtIndex:(NSInteger)index; @end
3.設置tabBarItem點擊事件
因為我們的tabBarItem是繼承自UIButton,所以這邊用addtarget的方式為每一個item都添加了事件響應機制。代碼如下:
- (void)tabBarItemWasSelected:(id)sender { if ([[self delegate] respondsToSelector:@selector(tabBar:shouldSelectItemAtIndex:)]) { NSInteger index = [self.items indexOfObject:sender]; if (![[self delegate] tabBar:self shouldSelectItemAtIndex:index]) { return; } } [self setSelectedItem:sender]; if ([[self delegate] respondsToSelector:@selector(tabBar:didSelectItemAtIndex:)]) { NSInteger index = [self.items indexOfObject:self.selectedItem]; [[self delegate] tabBar:self didSelectItemAtIndex:index]; } }
創建自定義CustomTabBarItem
關於自定義的按鈕,我會在下篇記錄中著重給大家講解一下,如何繪制一個精美的自定義的按鈕;代碼我先貼出來,可以先看一下:
#import@interface CustomTabBarItem : UIButton @property CGFloat itemHeight; #pragma mark - Title configuration @property (nonatomic, copy) NSString *title; @property (nonatomic) UIOffset titlePositionAdjustment; @property (copy) NSDictionary *unselectedTitleAttributes; @property (copy) NSDictionary *selectedTitleAttributes; #pragma mark - Image configuration @property (nonatomic) UIOffset imagePositionAdjustment; - (UIImage *)finishedSelectedImage; - (UIImage *)finishedUnselectedImage; - (void)setFinishedSelectedImage:(UIImage *)selectedImage withFinishedUnselectedImage:(UIImage *)unselectedImage; #pragma mark - Background configuration - (UIImage *)backgroundSelectedImage; - (UIImage *)backgroundUnselectedImage; - (void)setBackgroundSelectedImage:(UIImage *)selectedImage withUnselectedImage:(UIImage *)unselectedImage; #pragma mark - Badge configuration @property (nonatomic, copy) NSString *badgeValue; @property (strong) UIImage *badgeBackgroundImage; @property (strong) UIColor *badgeBackgroundColor; @property (strong) UIColor *badgeTextColor; @property (nonatomic) UIOffset badgePositionAdjustment; @property (nonatomic) UIFont *badgeTextFont; @end
#import "CustomTabBarItem.h" @interface CustomTabBarItem () { NSString *_title; UIOffset _imagePositionAdjustment; NSDictionary *_unselectedTitleAttributes; NSDictionary *_selectedTitleAttributes; } @property UIImage *unselectedBackgroundImage; @property UIImage *selectedBackgroundImage; @property UIImage *unselectedImage; @property UIImage *selectedImage; @end @implementation CustomTabBarItem - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { [self commonInitialization]; } return self; } - (id)init { return [self initWithFrame:CGRectZero]; } - (void)commonInitialization { // Setup defaults [self setBackgroundColor:[UIColor clearColor]]; _title = @""; _titlePositionAdjustment = UIOffsetZero; if (NSFoundationVersionNumber > NSFoundationVersionNumber_iOS_6_1) { _unselectedTitleAttributes = @{ NSFontAttributeName: [UIFont systemFontOfSize:10], NSForegroundColorAttributeName:[UIColor colorWithRed:255/255.f green:255/255.f blue:255/255.f alpha:1.0f], }; } else { #if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_7_0 _unselectedTitleAttributes = @{ UITextAttributeFont: [UIFont systemFontOfSize:10], UITextAttributeTextColor: [UIColor colorWithRed:255/255.f green:255/255.f blue:255/255.f alpha:1.0f], }; #endif } _selectedTitleAttributes = [_unselectedTitleAttributes copy]; _badgeBackgroundColor = [UIColor redColor]; _badgeTextColor = [UIColor whiteColor]; _badgeTextFont = [UIFont systemFontOfSize:12]; _badgePositionAdjustment = UIOffsetZero; } - (void)drawRect:(CGRect)rect { CGSize frameSize = self.frame.size; CGSize imageSize = CGSizeZero; CGSize titleSize = CGSizeZero; NSDictionary *titleAttributes = nil; UIImage *backgroundImage = nil; UIImage *image = nil; CGFloat imageStartingY = 0.0f; if ([self isSelected]) { image = [self selectedImage]; backgroundImage = [self selectedBackgroundImage]; titleAttributes = [self selectedTitleAttributes]; if (!titleAttributes) { titleAttributes = [self unselectedTitleAttributes]; } } else { image = [self unselectedImage]; backgroundImage = [self unselectedBackgroundImage]; titleAttributes = [self unselectedTitleAttributes]; } imageSize = [image size]; CGContextRef context = UIGraphicsGetCurrentContext(); CGContextSaveGState(context); [backgroundImage drawInRect:self.bounds]; // Draw image and title if (![_title length]) { [image drawInRect:CGRectMake(roundf(frameSize.width / 2 - imageSize.width / 2) + _imagePositionAdjustment.horizontal, roundf(frameSize.height / 2 - imageSize.height / 2) + _imagePositionAdjustment.vertical, imageSize.width, imageSize.height)]; } else { if (NSFoundationVersionNumber > NSFoundationVersionNumber_iOS_6_1) { titleSize = [_title boundingRectWithSize:CGSizeMake(frameSize.width, 20) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName: titleAttributes[NSFontAttributeName]} context:nil].size; imageStartingY = roundf((frameSize.height - imageSize.height - titleSize.height) / 2); [image drawInRect:CGRectMake(roundf(frameSize.width / 2 - imageSize.width / 2) + _imagePositionAdjustment.horizontal, imageStartingY + _imagePositionAdjustment.vertical, imageSize.width, imageSize.height)]; CGContextSetFillColorWithColor(context, [titleAttributes[NSForegroundColorAttributeName] CGColor]); [_title drawInRect:CGRectMake(roundf(frameSize.width / 2 - titleSize.width / 2) + _titlePositionAdjustment.horizontal, imageStartingY + imageSize.height + _titlePositionAdjustment.vertical, titleSize.width, titleSize.height) withAttributes:titleAttributes]; } else { #if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_7_0 titleSize = [_title sizeWithFont:titleAttributes[UITextAttributeFont] constrainedToSize:CGSizeMake(frameSize.width, 20)]; UIOffset titleShadowOffset = [titleAttributes[UITextAttributeTextShadowOffset] UIOffsetValue]; imageStartingY = roundf((frameSize.height - imageSize.height - titleSize.height) / 2); [image drawInRect:CGRectMake(roundf(frameSize.width / 2 - imageSize.width / 2) + _imagePositionAdjustment.horizontal, imageStartingY + _imagePositionAdjustment.vertical, imageSize.width, imageSize.height)]; CGContextSetFillColorWithColor(context, [titleAttributes[UITextAttributeTextColor] CGColor]); UIColor *shadowColor = titleAttributes[UITextAttributeTextShadowColor]; if (shadowColor) { CGContextSetShadowWithColor(context, CGSizeMake(titleShadowOffset.horizontal, titleShadowOffset.vertical), 1.0, [shadowColor CGColor]); } [_title drawInRect:CGRectMake(roundf(frameSize.width / 2 - titleSize.width / 2) + _titlePositionAdjustment.horizontal, imageStartingY + imageSize.height + _titlePositionAdjustment.vertical, titleSize.width, titleSize.height) withFont:titleAttributes[UITextAttributeFont] lineBreakMode:NSLineBreakByTruncatingTail]; #endif } } // Draw badges if ([[self badgeValue] length]) { CGSize badgeSize = CGSizeZero; if (NSFoundationVersionNumber > NSFoundationVersionNumber_iOS_6_1) { badgeSize = [_badgeValue boundingRectWithSize:CGSizeMake(frameSize.width, 20) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName: [self badgeTextFont]} context:nil].size; } else { #if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_7_0 badgeSize = [_badgeValue sizeWithFont:[self badgeTextFont] constrainedToSize:CGSizeMake(frameSize.width, 20)]; #endif } CGFloat textOffset = 2.0f; if (badgeSize.width < badgeSize.height) { badgeSize = CGSizeMake(badgeSize.height, badgeSize.height); } CGRect badgeBackgroundFrame = CGRectMake(roundf(frameSize.width / 2 + (image.size.width / 2) * 0.9) + [self badgePositionAdjustment].horizontal, textOffset + [self badgePositionAdjustment].vertical, badgeSize.width + 2 * textOffset, badgeSize.height + 2 * textOffset); if ([self badgeBackgroundColor]) { CGContextSetFillColorWithColor(context, [[self badgeBackgroundColor] CGColor]); CGContextFillEllipseInRect(context, badgeBackgroundFrame); } else if ([self badgeBackgroundImage]) { [[self badgeBackgroundImage] drawInRect:badgeBackgroundFrame]; } CGContextSetFillColorWithColor(context, [[self badgeTextColor] CGColor]); if (NSFoundationVersionNumber > NSFoundationVersionNumber_iOS_6_1) { NSMutableParagraphStyle *badgeTextStyle = [[NSMutableParagraphStyle defaultParagraphStyle] mutableCopy]; [badgeTextStyle setLineBreakMode:NSLineBreakByWordWrapping]; [badgeTextStyle setAlignment:NSTextAlignmentCenter]; NSDictionary *badgeTextAttributes = @{ NSFontAttributeName: [self badgeTextFont], NSForegroundColorAttributeName: [self badgeTextColor], NSParagraphStyleAttributeName: badgeTextStyle, }; [[self badgeValue] drawInRect:CGRectMake(CGRectGetMinX(badgeBackgroundFrame) + textOffset, CGRectGetMinY(badgeBackgroundFrame) + textOffset, badgeSize.width, badgeSize.height) withAttributes:badgeTextAttributes]; } else { #if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_7_0 [[self badgeValue] drawInRect:CGRectMake(CGRectGetMinX(badgeBackgroundFrame) + textOffset, CGRectGetMinY(badgeBackgroundFrame) + textOffset, badgeSize.width, badgeSize.height) withFont:[self badgeTextFont] lineBreakMode:NSLineBreakByTruncatingTail alignment:NSTextAlignmentCenter]; #endif } } CGContextRestoreGState(context); } #pragma mark - Image configuration - (UIImage *)finishedSelectedImage { return [self selectedImage]; } - (UIImage *)finishedUnselectedImage { return [self unselectedImage]; } - (void)setFinishedSelectedImage:(UIImage *)selectedImage withFinishedUnselectedImage:(UIImage *)unselectedImage { if (selectedImage && (selectedImage != [self selectedImage])) { [self setSelectedImage:selectedImage]; } if (unselectedImage && (unselectedImage != [self unselectedImage])) { [self setUnselectedImage:unselectedImage]; } } - (void)setBadgeValue:(NSString *)badgeValue { _badgeValue = badgeValue; [self setNeedsDisplay]; } #pragma mark - Background configuration - (UIImage *)backgroundSelectedImage { return [self selectedBackgroundImage]; } - (UIImage *)backgroundUnselectedImage { return [self unselectedBackgroundImage]; } - (void)setBackgroundSelectedImage:(UIImage *)selectedImage withUnselectedImage:(UIImage *)unselectedImage { if (selectedImage && (selectedImage != [self selectedBackgroundImage])) { [self setSelectedBackgroundImage:selectedImage]; } if (unselectedImage && (unselectedImage != [self unselectedBackgroundImage])) { [self setUnselectedBackgroundImage:unselectedImage]; } } @end