LNButton

自己写的,支持图片与文字的四种显示模式,支持原生Button属性

TabBar + 导航

TabBar

TabItem + TabBar
其中:

  • TabItem控制单个Tab的Icon显示等和对应的ViewController
  • TabBar容纳所有的Tab,并有delegate属性

每个Tab都是一个Button,通过设置点击事件,来控制所有Tab的selected和unSelected状态。
在点击事件里:

  • 通过[self setSelectedIndex:(sender.tag - ROOT_BUTTON_TAG)];控制selected状态的切换
  • 通过[delegate shouldSelectItemAtIndex][delegate didSelectItemAtIndex]来控制ViewController(NavigationController的切换)

导航功能

RootTabBarController作为TabBardelegate,实现代理方法。
其中didSelectItemAtIndex中调用[self setSelectedIndex:index]来控制VC的切换:

1
2
3
4
5
6
7
8
9
10
11
- (void)tabBar:(LNBaseTabBar *)tabBar didSelectItemAtIndex:(NSInteger)index {
if (index < 0 || index >= [[self navControllers] count]) {
return;
}

[self setSelectedIndex:index];

if ([[self delegate] respondsToSelector:@selector(tabBarController:didSelectViewController:)]) {
[[self delegate] tabBarController:self didSelectViewController:[self navControllers][index]];
}
}

其中setSelectedIndex的逻辑如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- (void)setSelectedIndex:(NSUInteger)selectedIndex {
if (selectedIndex >= self.navControllers.count) {
return;
}
if ([self selectedNavController]) {
[[self selectedNavController] willMoveToParentViewController:nil];
[[[self selectedNavController] view] removeFromSuperview];
[[self selectedNavController] removeFromParentViewController];
}

_selectedIndex = selectedIndex;
[self setSelectedNavController:[[self navControllers] objectAtIndex:selectedIndex]];
[self addChildViewController:[self selectedNavController]];
[[[self selectedNavController] view] setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
[[self contentView] addSubview:[[self selectedNavController] view]];
[[self selectedNavController] didMoveToParentViewController:self];

[self.view setNeedsLayout];
[self setNeedsStatusBarAppearanceUpdate];
}

夜间模式

LNThemePicker

Picker基类:

1
2
3
4
5
6
7
8
9
10
11
@interface BDHKThemePicker : NSObject

// 可能是UIColor、NSString、NSNumber数组
@property (nonatomic, readonly) NSArray * _Nullable array;
// 即将变成的模式下对应的值,可能是UIColor、NSString、NSNumber
@property (nonatomic, readonly) NSObject * _Nullable value;

+ (BDHKThemePicker * _Nonnull)pickerWithArray:(NSArray * _Nonnull)array;
- (instancetype _Nonnull)initWithArray:(NSArray * _Nonnull)array;

@end

ColorPicker子类:

1
2
3
4
5
6
7
8
9
10
11
12
@interface BDHKThemeColorPicker : BDHKThemePicker

@property (nonatomic, readonly) UIColor * _Nullable value;

+ (BDHKThemeColorPicker * _Nonnull)pickerWithColors:(NSArray<UIColor *> * _Nonnull)colors;
+ (BDHKThemeColorPicker * _Nonnull)pickerWithStrings:(NSArray<NSString *> * _Nonnull)colors;
+ (BDHKThemeColorPicker * _Nonnull)pickerWithNumbers:(NSArray<NSNumber *> * _Nonnull)colors;
- (instancetype _Nonnull)initWithColors:(NSArray<UIColor *> * _Nonnull)colors;
- (instancetype _Nonnull)initWithStrings:(NSArray<NSString *> * _Nonnull)colors;
- (instancetype _Nonnull)initWithWithNumbers:(NSArray<NSNumber *> * _Nonnull)colors;

@end

还有CGFloatPicker、ImagePicker等。

UIView的分类

首先对NSObject实现LNTheme的分类。
themePickers属性懒加载时,会通过关联对象获取字典值。如果没有获取到对应的关联对象,则新生成,同时设置通知及方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@interface NSObject (LNTheme)

// 设置方法及其参数对应的picker,
// 如@{
// @"setImage:"->@[@"#222222", @"#333333"],
// @"setBackgroundColor:"->@[@"#222222", @"#333333"]
// }
@property (nonatomic, strong) NSMutableDictionary *themePickers;

- (void)performThemePicker:(NSString *)selector picker:(BDHKThemePicker *)picker state:(UIControlState)state;
- (void)setThemePicker:(NSString *)selector picker:(BDHKThemePicker *)picker;
- (void)setThemePicker:(NSString *)selector picker:(BDHKThemePicker *)picker state:(UIControlState)state;
- (BDHKThemeStatePicker *)makeStatePicker:(NSString *)selector picker:(BDHKThemePicker *)picker state:(UIControlState)state;

@end

UIView、UIButton、UIImageView、UITableView、UILabel、UITextFeild、UINavigationBar等UI控件都实现了一个LNTheme的分类(Category)。
以UIImageView为例,其.h文件如下:

1
2
3
4
5
@interface UIImageView (BDHKTheme)

@property (nonatomic, strong) BDHKThemeImagePicker * _Nullable theme_image;

@end

.m文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
@implementation UIImageView (BDHKTheme)

- (BDHKThemeImagePicker *)theme_image
{
return [self.themePickers objectForKeyCheck:@"setImage:"];
}

- (void)setTheme_image:(BDHKThemeImagePicker *)theme_image
{
[self setThemePicker:@"setImage:" picker:theme_image];
}

@end

使用逻辑

讨论[UIImageView setImage:]的方法,在不同的模式下,显示不同的图片。
这主要用在一些APP内部图上,如用户头像的默认图(网络请求的图一般是不会用这个功能的)。
UIImageView对象img,通过设置theme_image这个属性(指定ImagePicker),来指定对应模式下应使用的图片。
而在theme_imageset方法总,会调用NSObject(LNTheme)分类下的setThemePicker:picker:方法,从而将系统方法setImage:与我们指定的picker对应上。
当我们切换模式时,将发送对应的通知,设置了picker的UI对象会调用[NSObject updateTheme]方法,遍历themePickers字典,为所有设置了对应picker的系统方法,更新UI显示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (void)updateTheme
{
for (NSString *selector in self.themePickers) {
BDHKThemePicker *picker = [self.themePickers objectForKeyCheck:selector class:[BDHKThemePicker class]];
if ([picker isKindOfClass:[BDHKThemeStatePicker class]]) {
BDHKThemeStatePicker *statePicker = (BDHKThemeStatePicker *)picker;
for (NSNumber *key in statePicker.values) {
[self updateThemeCell:selector picker:picker state:[key unsignedIntegerValue]];
}
}
else {
[self updateThemeCell:selector picker:picker state:0];
}
}
}

LNJastor

用于Model的转换,通过Runtime + KVC实现。

Runtime

KHJastorRuntimeHelper, 用于获取自定义的LNJastor子类有些什么属性,通过数组返回。
属性数组,数组中的单个元素类型为NSDictionary,有三个键值对:

  • type -> 自定义属性的类型:如@“NSString”、@“ProblemItem”
  • name -> 自定义属性的名称:如@“problemContent”、@“problemId”
  • attributes -> 自定义属性的系统属性:如@“T@“UserInfoItem”,&,N,V_userInfo”

运用了Runtime,如:

  • class_copyPropertyList(aClass, &itemCount)
  • property_getName(property)
  • property_getAttributes(property)

核心函数[KHJastorRuntimeHelper propertyNames:]如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
+ (NSArray *)propertyNames:(__unsafe_unretained Class)aClass
{
if (aClass == [LNJastor class]) {
return [NSArray array];
}

if (propertyListByClass == nil) {
propertyListByClass = [[LNThreadSafeMutableDictionary alloc] init];
}

NSString *className = NSStringFromClass(aClass);
NSArray *names = [propertyListByClass ln_arrayForKey:className];
if (names != nil) {
return names;
}

LNThreadSafeMutableArray *items = [LNThreadSafeMutableArray array];
unsigned int itemCount = 0;
objc_property_t *propertys = class_copyPropertyList(aClass, &itemCount);
for (unsigned int i = 0; i < itemCount; ++i) {
objc_property_t property = propertys[i];
const char *name = property_getName(property);

if (strcmp(name,"hash") == 0 ||
strcmp(name,"superclass") == 0 ||
strcmp(name,"class") == 0 ||
strcmp(name,"description") == 0 ||
strcmp(name,"debugDescription") == 0) {
continue;
}

const char *attribute = property_getAttributes(property);
NSArray *attributes = [[NSString stringWithUTF8String:attribute] componentsSeparatedByString:@","];
NSString *attributeType = [attributes objectAtIndex:0];
NSString *propertyType = [attributeType substringFromIndex:1];
NSString *typeName = [self propertyType:[propertyType UTF8String]];
if (typeName == nil && [attributeType hasPrefix:@"T@"]) {//T@\"NSString\"
typeName = [attributeType substringWithRange:NSMakeRange(3, [attributeType length] - 4)];
}

NSMutableDictionary *item = [NSMutableDictionary dictionary];
[item setObjectCheck:[NSString stringWithUTF8String:name] forKey:@"name"];
[item setObjectCheck:[NSString stringWithUTF8String:attribute] forKey:@"attributes"];
[item setObjectCheck:typeName forKey:@"type"];
[items addObjectCheck:item];
}
free(propertys);
[propertyListByClass setObjectCheck:items forKey:className];

NSArray *array = [LNJastorRuntimeHelper propertyNames:class_getSuperclass(aClass)];
if (![NSArray isEmpty:array]) {
[items addObjectsFromArray:array];
}
return items;
}

基类mapping方法,获取属性名->属性名字典:

1
2
3
4
5
6
7
8
9
10
- (NSDictionary *)mapping
{
NSArray *properties = [LNJastorRuntimeHelper propertyNames:[self class]];
NSMutableDictionary *maps = [[NSMutableDictionary alloc] initWithCapacity:properties.count];
for (NSDictionary *property in properties) {
NSString *propertyName = [property ln_stringForKey:@"name"];
[maps setObjectCheck:propertyName forKey:propertyName];
}
return maps;
}

自定义类mapping方法,获取属性名->后端字段名字典:

1
2
3
4
5
6
7
8
9
10
- (NSDictionary *)mapping {
NSMutableDictionary *maps = [NSMutableDictionary dictionaryWithDictionary:[super mapping]];

// key为属性名称,object为后端字段名称
[maps setObjectCheck:@"pId" forKey:@"p_id"];
[maps setObjectCheck:@"frontendId" forKey:@"frontend_id"];
[maps setObjectCheck:@"title" forKey:@"title"];

return maps;
}

设计逻辑

假如我们自定义的LNJastor子类为ProblemItem,在使用initWithDictionary:进行初始化时:
(ProblemItem自定义的所有属性,由一个属性数组properties表示,由RuntimeHelper提供。)

  1. 调用mapping方法:根据自定义的属性名与后端字段名的映射,生成一个字典maps,其元素形式为属性名->后端字段名
  2. 遍历properties:根据property的@"name"字段,获取自定义的属性名;maps根据属性名,获取后端字段;初始化字典根据后端字段,获取值。通过KVC,设置该属性的值。

刷新:MJRefresh

MJRefresh
一个刷新控件。
UIScrollView、UITableView、UICollectionView、UIWebView都可以使用。

空白页:EmptyTableView

DZNEmptyDataSet

DZNEmptyDataSet
一个使你的APP在UITableView和UICollectionView没有数据的时候不至于是白板的库。
主要思想是用swizzle,把reloadData方法置换掉。

SDWebImage

AFNetWorking

FMDB