献给初学iOS的小盆友们——微博app项目开发之一 项目初始化
本人自学iOS也有七八个月了,不敢说学到很深入了,但也算入了门。此次微博app项目参考了传智播客培训教材,主要学习内容有架构思想,封装思想,代码重构,业务逻辑等内容,项目涵盖面广泛,讲解易懂,且采用纯代码方式搭建UI,希望对那些没有时间看视频的初学者们有所帮助。相信学习完本套项目,初学者会在编程思想上有一个很大的提升。

内容
项目素材获取
环境配置
自定义tabBarController
修改tabBar内部结构
划分结构
本节资料
*节资料下载

1.1 项目素材获取
首先模仿一个项目,需要图片等素材,单凭自己是做不出来的。本项目提供了基本的图片素材供下载使用,一般项目素材的获取步骤如下:

打开苹果电脑iTunes应用,选择顶部栏“AppStore”项
然后在搜索框内输入“微博”并搜索
找到微博并点击,进去后点击微博图标下的“取得”按钮
输入 iCloud账户密码后即可下载
左上角下载按钮可以显示下载进度
点击顶部栏“我的iPhone应用按钮”
找到微博应用,右键点击,选择“在Finder中显示”
可以看到下载的是ipa类型的文件,使用解压文件解压后即可得到微博应用文件夹
进入文件夹后,选择Payload下的Weibo.app文件,右键点击后选择“显示包内容”,即可看到微博应用所需的所有图片,以后项目模仿都会用得到
1.2 环境配置
开发任何一个大型的应用都需要提前对开发环境进行配置,本次微博项目对Xcode进行了能满足我们模仿要求的简单设置。配置过程也就是修改info.plist文件而已,点击“微博模拟”项目出现的设置页面就是info.plist的图形化界面。配置过程如下:

Bundle Identifier 设置
Bundle Identifier 主要作用有app在上传app store时为了区分不同程序 时使用,开发推送功能时需要,在这里设置成YGWeibo.- – – – 。
Version 版本号
以后迭代开发时,版本号必须比之前的大,在这里不需要设置
Development Target
选择7.0以后的都可以
Devices
选择iPhone
Main Interface
此次项目采用纯代码创建,所以不需要加载storyboard,在这里设置为空,并且把左侧Main.Storyboard,ViewController.h,ViewController.m 文件删除。
Device orientation
只选择portrait
Status Bar Style
选择default后,勾选Hide Status Bar
这里讲一讲,怎么用纯代码得到跟加载main.storyboard有相同效果的界面。首先苹果应用程序的启动步骤是这样的,在一开启时,首先进入main函数,main 函数内主要执行三个步骤,首先创建UIApplication对象,然后创建AppDelegate对象,并且成为UIApplication对象的代理属性,然后开启主线程循环,*后加载info.plist文件,判断是否有main.storyboard,如果有,就会加载main.storyboard。因为我们这里才用纯代码开发,所以info.plist就没有main.storyboard文件了,需要在AppDelegate.m里的*个代理方法中设置窗口,以及创建并加载视图控制器,代码如下,但是此代码非*终的tabBarVC的设置代码,以后会有修改此处只做演示用。此代码就相当于加载Storyboard的步骤。

– (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// 创建窗口
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];

// 为了展示效果 设置背景色为黄色
self.window.backgroundColor = [UIColor yellowColor];

// 创建tabBarViewController
UITabBarController *tabBarVc = [[UITabBarController alloc] init];
tabBarVc.view.backgroundColor = [UIColor redColor];

// 管理子控制器
// 首页
UIViewController *home = [[UIViewController alloc] init];
home.view.backgroundColor = [UIColor greenColor];
[tabBarVc addChildViewController:home];

// 消息
UIViewController *message = [[UIViewController alloc] init];
message.view.backgroundColor = [UIColor blueColor];
[tabBarVc addChildViewController:message];

// 发现
UIViewController *discover = [[UIViewController alloc] init];
discover.view.backgroundColor = [UIColor purpleColor];
[tabBarVc addChildViewController:discover];

// 我
UIViewController *profile = [[UIViewController alloc] init];
profile.view.backgroundColor = [UIColor lightGrayColor];
[tabBarVc addChildViewController:profile];

// 设置窗口的根控制器
self.window.rootViewController = tabBarVc;

// 显示窗口
[self.window makeKeyAndVisible];
return YES;
}

设置AppIcon
直接拖拽素材文件夹内的AppIcon文件夹到Xcode里Assets.xcassets内的AppIcon里即可
设置启动图片
在项目General 设置里面找到Launch Images Source 栏,点击后出现对话框,选择migrate,然后点击其后的灰色按钮,会发现自动跳转到Brand Assets 栏内。打开素材文件夹中的LaunchImages文件夹,拖拽文件到Brand Assets 内,出现绿色加号后放开即可,*后删除Launch Screen File 栏内的东西。
但是这种方法不如直接加载Launch Screen.storyboard文件夹更强大,因为Launch Screen.storyboard可以展示更多的东西,且不需要美工出很多启动图片,因为其可以自动布局以适应各种大小屏幕。如果不删除Launch Screen.storyboard,则会优先加载Launch Screen.storyboard内容。
1.3 自定义tabBarController
1.3.1 更改AppDelegate.m
因为AppDelegate.m文件以后会越来越大,应该自定义一个类专门用于创建和管理tabBarController。在新建文件之前,要设置个前缀,以方便区别和管理。选择左边栏微博模拟项目,找到*右边栏有个“Class Prefix”栏,写上你想起的前缀名称即可,如下图所示。

更改AppDelegate.m内容为如下代码:(首先要新建YGTabBarController)

#import “AppDelegate.h”
#import “YGTabBarController.h”
@interface AppDelegate ()

@end

@implementation AppDelegate

– (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
// 创建窗口

self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];

//创建自定义tabbarcontroller

YGTabBarController *tabBarVc = [[YGTabBarController alloc]init];

// 设置窗口的根控制器

self.window.rootViewController = tabBarVc;

// 显示窗口

[self.window makeKeyAndVisible];
return YES;
}

在此,提醒下UITabBarController内的view在一创建控制器的时候[[YGTabBarController alloc]init]就会加载View,并执行ViewDidLoad方法,所以其View不是懒加载的。但是一般的UIViewController的View是懒加载的。

1.3.2 新建YGTabBarController。
新建一个YGTabBarController类继承自UITabBarController后,然后把之前在AppDelegate内创建与加载子控制器的代码封装在YGTabBarController.m中,并把tabBarVc更改为self。
本小节主要任务就是为了代码的简洁性,抽取两个方法,一个是从ViewDidLoad中抽取设置tabBarController子控件的方法:

-(void)setUpAllChildViewController

因为每加一个子控制器所写的代码都是类似的,所以又在上个方法内又抽取了设置一个子控件的方法。因为每个子控制器都要设置标题,颜色,图片以及其他属性,所以把这些属性都设置为参数传进到方法内。这里提醒一下,如果参数中会传入中文的话,一般把此参数放到*后,例如title参数。

– (void)setUpOneChildViewController:(UIViewController *)vc image:(UIImage *)image selectedImage:(UIImage *)selectedImage title:(NSString *)title

下面就要讲如何设置tabBar按钮上面的文字与图片了。
首先tabBar上的按钮是由对应的子控制器的tabBarItem属性决定。导入素材中tabBar文件夹拖入到项目中。设定按钮title和image。在ios7之后,默认会把UITabBar上面的按钮图片渲染成蓝色,但大多情况下蓝色不是我们想要的颜色,我们想要让tabBar用美工设计好的图片颜色。更改的方法可以是,如图片所示, 把每张带有selected字符后缀的图片的Render As 属性设置为Original Image。

或者用代码更改,在这里我们就写一个UIImage的分类——UIImage+Image.h/.m。目的是为了让以后也方便设置图片渲染。代码如下:

#import “UIImage+image.h”

@implementation UIImage (image)

+ (instancetype)imageWithOriginalName:(NSString *)imageName
{
UIImage *image = [UIImage imageNamed:imageName];

return [image imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
}

@end

 

这里对上面的代码做下说明:为什么要使用工厂方法(返回值为instancetype)?因为其默认识别是哪个类调用此方法,并返回该类的对象。

然后在setUpOneChildViewController方法里设置按钮标题等内容。代码如下:

#pragma mark – 添加一个子控制器
– (void)setUpOneChildViewController:(UIViewController *)vc image:(UIImage *)image selectedImage:(UIImage *)selectedImage title:(NSString *)title
{
vc.tabBarItem.title = title;
vc.tabBarItem.image = image;
vc.tabBarItem.badgeValue = @”10″;
vc.tabBarItem.selectedImage = selectedImage;
[self addChildViewController:vc];
}

 

但是在设置字体颜色的时候你会发现,不能用self.tabBarItem.textColor 来设置标题颜色,因为UITabBarItem是模型而不是控件,模型是用来保存数据以决定tabBar上按钮的内容。只有控件才有textColor属性,例如UILabel。所以通过观察UITabBarItem的头文件以及其父类的头文件,发现可以用setTitleTextAttributes 方法 也就是富文本来设置标题颜色。富文本不仅可以设置控件的文字颜色,也可以设置字体空心,阴影,图文混排等。通过UIKit框架内的NSAttributesString.h头文件,可以查找到设置富文本字典等key。这里我们采用全局方法+ initialize 来设置颜色。但在+load方法内也可以。但是两者的调用事件不同,+initialize方法是在*次使用这个类或者子类的时候调用。+load方法是在程序一启动的时候就调用,然后把所有的类加载到内存,且先于main函数执行。

initialize 代码如下:

+ (void)initialize
{
// 这行代码是获取所有的tabBarItem外观标识,这样就容易把不希望改动到外观也更改了
// UITabBarItem *item = [UITabBarItem appearance];
//一般采用带 self的方法,这里self就是指 YGTabBarController

// 获取当前这个类下面的所有tabBarItem
UITabBarItem *item = [UITabBarItem appearanceWhenContainedIn:self, nil];
NSMutableDictionary *att = [NSMutableDictionary dictionary];

//这里也可以用[att setObject:[UIColor orangeColor] forKey:NSForegroundColorAttributeName];
//来设置富文本字典,两种方法都可以。
att[NSForegroundColorAttributeName] = [UIColor orangeColor];

[item setTitleTextAttributes:att forState:UIControlStateSelected];
}

 

1.4 修改tabBar内部结构
在观察实际微博界面会发现,tabBar中间有个用来发微博的加号按钮。这就需要首先调整系统tabBar 结构,改变子控件位置,然后在中间加上加号按钮 。系统的tabBar按钮位置是不能改动的,所以智能自定义一个YGTabBar,继承自UITabBar。然后把系统TabBar替换成自定义的YGTabBar。代码如下:

– (void)viewDidLoad {
[super viewDidLoad];
//添加子控制器
[self setUpAllChildViewController];

// 自定义tabBar
YGTabBar *tabBar = [[YGTabBar alloc] initWithFrame:self.tabBar.frame];

// 利用KVC更改readonly的属性
[self setValue:tabBar forKeyPath:@”tabBar”];
}

代码说明: 这里有两点需要注意,一要判断在哪个方法里去替换系统tabBar。经过测试发现,系统tabBar上的button是在执行完ViewDidLoad 和ViewWillAppear方法后才添加上去的。如下图所示:

从上如可以看到没给方法执行的顺序,所以我们要在添加tabBarButton前替换系统tabBar,这样的话,tabBarButton就会添加到自定义的YZTabBar上面。

第二个问题就是,但是这样赋值self.tabBar == tabBar 会出现错误。因为tabBar是readOnly属性,不能这样赋值。但是我们可以使用KVC赋值,如上面代码所示。

替换完系统tabBar之后,就要重写YGTabBar.m 内的 layOutSubviews方法,来计算每个item的位置。在重写之前先添加一个发微博按钮。代码如下:

– (UIButton *)plusButton
{
if (_plusButton == nil) {

UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
[btn setImage:[UIImage imageNamed:@”tabbar_compose_icon_add”] forState:UIControlStateNormal];
[btn setImage:[UIImage imageNamed:@”tabbar_compose_background_icon_add”] forState:UIControlStateHighlighted];
[btn setBackgroundImage:[UIImage imageNamed:@”tabbar_compose_button”] forState:UIControlStateNormal];
[btn setBackgroundImage:[UIImage imageNamed:@”tabbar_compose_button_highlighted”] forState:UIControlStateHighlighted];

// 默认按钮的尺寸跟背景图片一样大
// sizeToFit:默认会根据按钮的背景图片或者image和文字计算出按钮的*合适的尺寸
[btn sizeToFit];

_plusButton = btn;

[self addSubview:_plusButton];

}
return _plusButton;

 

重写layoutsubviews方法,调整子控件位置,代码如下:

– (void)layoutSubviews
{

[super layoutSubviews];
CGFloat w = self.bounds.size.width;
CGFloat h = self.bounds.size.height;

CGFloat btnX = 0;
CGFloat btnY = 0;
CGFloat btnW = w / (self.items.count + 1);
CGFloat btnH = self.bounds.size.height;
int i = 0;
// 调整系统自带的tabBar上的按钮位置
for (UIView *tabBarButton in self.subviews) {
// 判断下是否是UITabBarButton
if ([tabBarButton isKindOfClass:NSClassFromString(@”UITabBarButton” )]) {
if (i == 2) {
i = 3;
}
btnX = i * btnW;
tabBarButton.frame = CGRectMake(btnX, btnY, btnW, btnH);
i++;
}
}
// 设置添加按钮的位置
self.plusButton.center = CGPointMake(w * 0.5, h * 0.5);
}

 

代码说明:在取出tabBar每个button子控件时,需要判断类,可以利用NSClassFromString来根据一个字符串得出其类名。在interface 内添加一个UIButton 属性,并且懒加载,利用sizeToFit来默认按钮尺寸和背景图片一样大。但是我们发现tabBar上的UIBadgeView (如图所示)是继承自UIView的,是系统自带的,没法通过设置图片的方法更改。下一个微博将会讲到如何设置自定义的badgeView。

1.5 划分结构
YGTabbar上现在有五个模块,每个模块都有自己的功能,如果像我们这样直接创建每个模块的ViewController,就不能重写每个控制器的viewDidLoad方法,也就没法添加每个模块的各种功能了。这就需要我们为每个模块创建各自的MVC文件,划分结构如图所示: