上篇我们实现了架构代码生成器, 顿时觉得代码生成器这个东西只要掌握了写代码真的就是配置的问题了, 今天我们就来实现我一直以来就想做的功能 – UI界面适配代码生成器, 和之前一样我们通过JSON配置和Python来进行实现, 没看过上一篇的直接看这篇即可, 主要是思想.
参考链接:
上篇有一个内存泄露的问题, 需要将P层的模板的.h属性改为weak即可, 适配UI界面代码生成器的模板是我一直以来的的OC编码习惯, 代码风格无需强求, 直接修改模板即可. 我们先来看UI层的生成器目录结构:
生成器目录结构
├── UITemplate
│ ├── CellAdapterTemplate.h
│ ├── CollectionViewCellTemplate.h
│ ├── CollectionViewCellTemplate.m
│ ├── CollectionViewTemplate.h
│ ├── CollectionViewTemplate.m
│ ├── SubmodelTemplate.h
│ ├── SubmodelTemplate.m
│ ├── SubviewTemplate.h
│ ├── SubviewTemplate.m
│ ├── TableViewCellTemplate.h
│ └── TableViewCellTemplate.m
├── builder.py
└── config.json
CellAdapterTemplate.h
#import <Foundation/Foundation.h>
@protocol <#Unit#>CellAdapter <NSObject>
<#ModelInterface#>
@end
- cell的数据适配协议, 也就是适配器模式, 生成不依赖模型的Cell
TableViewCellTemplate.h
#import <UIKit/UIKit.h>
#import "<#Unit#>CellAdapter.h"
@interface <#Unit#>Cell : UITableViewCell
@property (nonatomic,strong) id<<#Unit#>CellAdapter> adapter;
+ (instancetype)cellWithTableView:(UITableView *)tableView;
+ (CGFloat)cellHeight;
@end
- 将cell的实现和高度屏蔽在内
TableViewCellTemplate.m
#import "<#Unit#>Cell.h"
<#ViewImport#>
@interface <#Unit#>Cell ()
<#ViewProperty#>
@end
@implementation <#Unit#>Cell
- (void)dealloc {
NSLog(@"%@ - execute %s",NSStringFromClass([self class]),__func__);
}
+ (instancetype)cellWithTableView:(UITableView *)tableView {
NSString * identifier = NSStringFromClass([<#Unit#>Cell class]);
<#Unit#>Cell * cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if (!cell) {
cell = [[<#Unit#>Cell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
}
return cell;
}
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
[self setupSubviews];
}
return self;
}
<#ViewLazyLoad#>
- (void)setupSubviews {
<#ViewSetup#>
}
- (void)setAdapter:(id<<#Unit#>CellAdapter>)adapter {
_adapter = adapter;
}
- (void)layoutSubviews {
[super layoutSubviews];
<#ViewLayout#>
}
+ (CGFloat)cellHeight {
return <#cellHeight#>;
}
@end
- cell的基本实现, 没有什么好说的.
SubviewTemplate.h
#import <UIKit/UIKit.h>
<#ClassImport#>
@interface <#Unit#>View : UIView
<#ViewProperty#>
@end
- 将View的属性暴露在外供外部修改
SubviewTemplate.m
#import "<#Unit#>View.h"
<#ViewImport#>
@implementation <#Unit#>View
- (void)dealloc {
NSLog(@"%@ - execute %s",NSStringFromClass([self class]),__func__);
}
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self setupSubviews];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
if (self) {
[self setupSubviews];
}
return self;
}
<#ViewLazyLoad#>
- (void)setupSubviews {
<#ViewSetup#>
}
- (void)layoutSubviews {
[super layoutSubviews];
<#ViewLayout#>
}
@end
- 自定义View的基本实现
CollectionViewTemplate.h
#import <UIKit/UIKit.h>
@interface <#Unit#>View : UIView
@property (nonatomic,strong) NSArray * dataSourceArr;
@end
- 传入可用data数组, 不在意是否为模型或字典.
CollectionViewTemplate.m
#import "<#Unit#>View.h"
#import "<#Unit#>Cell.h"
@interface <#Unit#>View () <UICollectionViewDataSource, UICollectionViewDelegate>
@property (nonatomic,strong) UICollectionView * collectionView;
@property (nonatomic,strong) UICollectionViewFlowLayout * collectionViewLayout;
@end
@implementation <#Unit#>View
- (void)dealloc {
NSLog(@"%@ - execute %s",NSStringFromClass([self class]),__func__);
}
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self setupSubviews];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
if (self) {
[self setupSubviews];
}
return self;
}
- (UICollectionView *)collectionView {
if (!_collectionView) {
_collectionView = [[UICollectionView alloc]initWithFrame:CGRectZero collectionViewLayout:self.collectionViewLayout];;
_collectionView.dataSource = self;
_collectionView.delegate = self;
_collectionView.backgroundColor = [UIColor whiteColor];
_collectionView.pagingEnabled = YES;
_collectionView.showsHorizontalScrollIndicator = NO;
[_collectionView registerClass:[<#Unit#>Cell class] forCellWithReuseIdentifier:NSStringFromClass([<#Unit#>Cell class])];
}
return _collectionView;
}
- (UICollectionViewFlowLayout *)collectionViewLayout {
if (!_collectionViewLayout) {
_collectionViewLayout = [UICollectionViewFlowLayout new];
_collectionViewLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
_collectionViewLayout.minimumInteritemSpacing = 0.0f;
_collectionViewLayout.minimumLineSpacing = 0.0f;
}
return _collectionViewLayout;
}
- (void)setupSubviews {
[self addSubview:self.collectionView];
}
- (void)setDataSourceArr:(NSArray *)dataSourceArr {
_dataSourceArr = dataSourceArr;
[_collectionView reloadData];
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return _dataSourceArr.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
<#Unit#>Cell * cell = [collectionView dequeueReusableCellWithReuseIdentifier:NSStringFromClass([<#Unit#>Cell class]) forIndexPath:indexPath];
return cell;
}
- (void)layoutSubviews {
[super layoutSubviews];
_collectionView.frame = self.bounds;
_collectionViewLayout.itemSize = <#size#>;
}
@end
- collectionView的基本实现, 实现最基本的功能.
CollectionViewCellTemplate.h
#import <UIKit/UIKit.h>
#import "<#Unit#>CellAdapter.h"
@interface <#Unit#>Cell : UICollectionViewCell
@property (nonatomic,strong) id<<#Unit#>CellAdapter> adapter;
@end
- 使用适配器加载数据.
CollectionViewCellTemplate.m
#import "<#Unit#>Cell.h"
<#ViewImport#>
@interface <#Unit#>Cell ()
<#ViewProperty#>
@end
@implementation <#Unit#>Cell
- (void)dealloc {
NSLog(@"%@ - execute %s",NSStringFromClass([self class]),__func__);
}
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self setupSubviews];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
if (self) {
[self setupSubviews];
}
return self;
}
<#ViewLazyLoad#>
- (void)setupSubviews {
<#ViewSetup#>
}
- (void)setAdapter:(id<<#Unit#>CellAdapter>)adapter {
_adapter = adapter;
}
- (void)layoutSubviews {
[super layoutSubviews];
<#ViewLayout#>
}
@end
- 一样的基本实现, 不多说.
SubmodelTemplate.h
#import <Foundation/Foundation.h>
@interface <#SubUnit#>Model : NSObject
<#ModelInterface#>
- (instancetype)initWithDictionary:(NSDictionary *)dictionary;
+ (<#SubUnit#>Model *)modelWithDictionary:(NSDictionary *)dictionary;
@end
SubmodelTemplate.m
#import "<#SubUnit#>Model.h"
@implementation <#SubUnit#>Model
- (instancetype)initWithDictionary:(NSDictionary *)dictionary {
self = [super init];
if (self) {
[self setValuesForKeysWithDictionary:dictionary];
}
return self;
}
+ (<#SubUnit#>Model *)modelWithDictionary:(NSDictionary *)dictionary {
return [[<#SubUnit#>Model alloc]initWithDictionary:dictionary];
}
@end
- 和上篇的model相同, 这篇为自动生成子模型. 为了灵活性单独生成.
说实话, 上述的UI层的界面重复的代码写的都快吐了, 开发过程中大多数时间就是花费在搭建UI界面上, 重复劳动, 真是醉了, 还好现在有了代码生成器这种神器, 写界面真的是分分钟搞定, 省下大把时间学习新东西.
config.json
{
"tableViewCell" : "Yes", //是否生成TableViewCell
"collectionViewCell" : "No", //是否生成CollectionViewCell
"subview" : "No", //是否生成自定义View
"submodel" : "No", //是否生成子Model
"isImport" : "Yes", //是否需要导入自定义View
"unit" : "HYBlockFive", //生成单元的名称
"bean" : "Goods", //生成子模型的名称
"layout" : { //文件布局的配置
"blockFiveView" : "HYBlockFiveView",
"textLabel" : "UILabel"
},
"model" : { //子模型和上篇模型相同
"text" : "String",
"detailText" : "String",
"imageUrl" : "String",
"models" : "Array",
"price" : "Float"
}
}
生成器的实现
builder.py
import os, sys, json
def cur_file_dir():
path = sys.path[0]
if os.path.isdir(path):
return path
elif os.path.isfile(path):
return os.path.dirname(path)
file_json = open(cur_file_dir() + "/config.json", "r")
data_json = json.loads(file_json.read())
bean = str(data_json["bean"])
unit = str(data_json["unit"])
isImport = str(data_json["isImport"])
submodel = data_json["submodel"]
subview = data_json["subview"]
tableViewCell = data_json["tableViewCell"]
collectionViewCell = data_json["collectionViewCell"]
viewProperty = '' //view的属性
viewLazyLoad = '' //懒加载
viewSetup = '' //添加到superview
viewLayout = '' //view的布局
viewImport = '' //引入头文件
classImport = '' //引入class
for key in data_json["layout"]: //进行布局代码的生成
value = data_json["layout"][key]
viewProperty += "@property (nonatomic,strong) " + str(value) + " * " + str(key) + ";\n"
viewLazyLoad += "- (" + str(value) + " *)" + str(key) + " {\n\n if (!_" + str(key) + ") {\n _" + str(key) + " = [" + str(value) + " new];\n }\n return _" + str(key) + ";\n}\n\n"
viewSetup += "\n [self addSubview:self." + str(key) + "];"
viewLayout += " CGFloat " + str(key) + "X = <#length#>;\n CGFloat " + str(key) + "Y = <#length#>;\n CGFloat " + str(key) + "W = <#length#>;\n CGFloat " + str(key) + "H = <#length#>;\n _" + str(key) + ".frame = CGRectMake(" + str(key) + "X, " + str(key) + "Y, " + str(key) + "W, " + str(key) + "H);\n\n"
if isImport == 'Yes':
viewImport += '#import "' + str(value) + '.h"\n'
classImport += '@class ' + str(value) + ';\n'
model = ''
for key in data_json["model"]:
type = ''
modified = ''
value = data_json["model"][key]
if value == 'String':
type = 'NSString * '
modified = 'copy'
elif value == 'Int':
type = 'NSInteger '
modified = 'assign'
elif value == 'Float':
type = 'CGFloat '
modified = 'assign'
elif value == 'Bool':
type = 'BOOL '
modified = 'assign'
elif value == 'Array':
type = 'NSArray * '
modified = 'strong'
elif value == 'Dictionary':
type = 'NSDictionary * '
modified = 'strong'
else:
type = value
modified = 'strong'
model += '\n@property (nonatomic,' + modified + ') ' + type + str(key) + ';'
file_json.close()
def builder(path, file, isbean):
r = open(cur_file_dir() + '/UITemplate/' + path, 'r')
d = r.read()
r.close()
if isbean == 'Yes': //判断输出的文件名是模型还是其他
w = open(cur_file_dir() + '/' + bean + file, 'w')
else:
w = open(cur_file_dir() + '/' + unit + file, 'w')
w.write(d.replace("<#ViewProperty#>", viewProperty).replace("<#Unit#>", unit).replace("<#ViewLazyLoad#>", viewLazyLoad).replace("<#ViewSetup#>",viewSetup).replace("<#ViewLayout#>",viewLayout).replace("<#SubUnit#>", bean).replace("<#ViewImport#>", viewImport).replace("<#ModelInterface#>", model).replace("<#ClassImport#>", classImport))
w.close()
if subview == "Yes": //生成自定义View
builder("SubviewTemplate.h", "View.h", "No")
builder("SubviewTemplate.m", "View.m", "No")
if tableViewCell == "Yes": //生成TableViewCell
builder("TableViewCellTemplate.h", "Cell.h", "No")
builder("TableViewCellTemplate.m", "Cell.m", "No")
builder("CellAdapterTemplate.h", "CellAdapter.h", "No")
if submodel == 'Yes': //生成子模型
builder("SubmodelTemplate.h", "Model.h", "Yes")
builder("SubmodelTemplate.m", "Model.m", "Yes")
if collectionViewCell == 'Yes': //生成CollectionViewCell
builder("CollectionViewTemplate.h", "View.h", "No")
builder("CollectionViewTemplate.m", "View.m", "No")
builder("CollectionViewCellTemplate.h", "Cell.h", "No")
builder("CollectionViewCellTemplate.m", "Cell.m", "No")
builder("CellAdapterTemplate.h", "CellAdapter.h", "No")
- 这样通过在终端中 python builder.py, 就能根据JSON表进行UI界面的的生成了.
彩蛋:
最近需要进行首页的改版, 我就使用这套生成器来练练手, 先贴下产品原型:
实测完成专题页一共生成53个文件, 仅需修改布局和导入, 完成用时30分钟左右.
代码库
当然我们写代码的时候也不能过分依赖代码生成工具, 我们还需要一些常用的代码库, 将我常用的分享与你.
//UIView
- (void)dealloc {
#if DEBUG
NSLog(@"--------");
NSLog(@"%@ - execute %s",NSStringFromClass([self class]),__func__);
NSLog(@"--------");
#endif
}
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self setupSubviews];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
if (self) {
[self setupSubviews];
}
return self;
}
- (void)setupSubviews {
}
- (void)loadSubviews:(id)dataSource {
}
- (void)layoutSubviews {
[super layoutSubviews];
}
//UITableViewCell
- (void)dealloc {
#if DEBUG
NSLog(@"--------");
NSLog(@"%@ - execute %s",NSStringFromClass([self class]),__func__);
NSLog(@"--------");
#endif
}
+ (instancetype)cellWithTableView:(UITableView *)tableView {
NSString * identifier = NSStringFromClass([<#class#> class]);
<#class#> * cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if (!cell) {
cell = [[<#class#> alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
}
return cell;
}
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
[self setupSubviews];
}
return self;
}
- (void)setupSubviews {
}
- (void)loadSubviews:(id)dataSource {
}
- (void)layoutSubviews {
[super layoutSubviews];
}
+ (CGFloat)cellHeight {
return <#height#>
}
//Define Peoperty
@property (nonatomic,<#modify#>) <#statement#>;
//Weak Self
__weak typeof(self) _self = self;
//Lazy Load
- (<#class#> *)<#name#> {
if (!_<#name#>) {
_<#name#> = <#initialize#>;
}
return _<#name#>;
}
//Frame Layout
CGFloat <#name#>X = <#length#>;
CGFloat <#name#>Y = <#length#>;
CGFloat <#name#>W = <#length#>;
CGFloat <#name#>H = <#length#>;
_<#name#>.frame = CGRectMake(<#name#>X, <#name#>Y, <#name#>W, <#name#>H);
//Debug
#if DEBUG
NSLog(@"--------");
NSLog(@“<#string#> - %<#encode#>",<#param#>);
NSLog(@"--------");
#endif
//KeyBoard Events
- (void)setupNotificationCenter {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillChangeFrame:)
name:UIKeyboardWillChangeFrameNotification object:nil];
}
- (void)removeNotificationCenter {
[[NSNotificationCenter defaultCenter]removeObserver:self];
}
- (void)keyboardWillChangeFrame:(NSNotification *)notification {
<#view#>.backgroundColor = self.backgroundColor;
CGFloat duration = [notification.userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
CGRect keyboardFrame = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
CGFloat transformY = keyboardFrame.origin.y - <#view#>.frame.size.height;
[UIView animateWithDuration:duration animations:^{
<#view#>.transform = CGAffineTransformMakeTranslation(0, transformY * 0.4f);
}];
}
可以发现, 这次的UI界面生成器就是根据我常用的代码库制作的, 以后就可以以生成为主, 代码库为辅了, 哈哈!