移動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 <NSObject>
- (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 <UIKit/UIKit.h>
@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