移动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