在上篇文章Graver绘制中,我们知道了Graver把复杂的界面变成了图片,那局部怎么交互呢?比如我们点击图片中的文字。
- 首先
WMMutableAttributedItem
是最小绘制单元,也就是它绘制我们点击的文字,WMMutableAttributedItem
里有WMGTextAttachment
数组,WMGTextAttachment
是附件(图片、事件响应),由WMGTextAttachment
来处理事件:
- 我们先从添加入手,主要是保存
target
和action
:
@implementation WMGTextAttachment
...
- (void)addTarget:(id)target action:(SEL)action forControlEvents:(UIControlEvents)controlEvents
{
_target = target;
_selector = action;
_responseEvent = (_target && _selector) && [_target respondsToSelector:_selector];//文本组件是否响应事件
}
- 点击响应时,通过
touchesBegan
判定激活区域:
@implementation WMGMixedView
...
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[_textDrawer touchesBegan:touches withEvent:event];//事件传递
//判断激活区
if (!_textDrawer.pressingActiveRange)
{
[super touchesBegan:touches withEvent:event];//父类传递
}
}
@implementation WMGTextDrawer
...
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UIView *contextView = [self eventDelegateContextView];
if (!contextView) { ... }
const CGPoint location = [[touches anyObject] locationInView:contextView];
const CGPoint layoutLocation = [self convertPointToLayout:location offsetPoint:_drawOrigin];
//当前被激活的区域
id<WMGActiveRange> activeRange = [self rangeInRanges:[self eventDelegateActiveRanges] forLayoutLocation:layoutLocation];
if (activeRange) {
[self setPressingActiveRange:activeRange];//设置
[contextView setNeedsDisplay];
}
_touchesBeginPoint = location;
}
- 接着在
touchesEnded
时进行响应:
@implementation WMGTextDrawer
...
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
if (_lastTouchEndedTimeStamp!= event.timestamp) {
self.savedPressingActiveRange = nil;
_lastTouchEndedTimeStamp = event.timestamp;
if (self.pressingActiveRange) {
id<WMGActiveRange> activeRange = self.pressingActiveRange;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.01 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self eventDelegateDidPressActiveRange:activeRange];
});
...
}
}
}
...
- (void)eventDelegateDidPressActiveRange:(id<WMGActiveRange>)activeRange
{
if (_eventDelegateHas.didPressActiveRange) {
[_eventDelegate textDrawer:self didPressActiveRange:activeRange];
}
}
- 最后通过代理响应方法:
@implementation WMGMixedView
...
- (void)textDrawer:(WMGTextDrawer *)textDrawer didPressActiveRange:(id<WMGActiveRange>)activeRange
{
if (activeRange.type == WMGActiveRangeTypeAttachment) {
WMGTextAttachment *att = (WMGTextAttachment *)activeRange.bindingData;//附件(图片,事件响应)
[att handleEvent:self];//处理事件
}
}
@implementation WMGTextAttachment
...
- (void)handleEvent:(id)sender
{
if (_target && _selector) {
if ([_target respondsToSelector:_selector]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[_target performSelector:_selector withObject:sender];//响应方法
#pragma clang diagnostic pop
}
}
}
- 我们回头来看看第2步,如果没有激活区域,那便会进行事件传递:
@implementation WMGCanvasControl
...
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
_touchInside = YES;
_tracking = [self beginTrackingWithTouch:touch withEvent:event];//是否需要追逐事件
self.highlighted = YES;
if (_tracking)
{
UIControlEvents currentEvents = UIControlEventTouchDown;
if (touch.tapCount > 1) {
currentEvents |= UIControlEventTouchDownRepeat;
}
[self _sendActionsForControlEvents:currentEvents withEvent:event];//事件的传递
}
}
...
- (void)_sendActionsForControlEvents:(UIControlEvents)controlEvents withEvent:(UIEvent *)event
{
for(__WMGCanvasControlTargetAction *t in [self _targetActions])
{
if(t.controlEvents == controlEvents)
{
if(t.target && t.action)
{
[self sendAction:t.action to:t.target forEvent:nil];
}
}
}
}
...
- (void)sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event
{
[[UIApplication sharedApplication] sendAction:action to:target from:self forEvent:event];
}
- 到这里我们可以发现,它是模仿了系统的事件传递:
- 补充
Graver为了更好地在tableView
上使用,提供了一个基类WMGBaseViewModel
。WMGBaseViewModel
负责与Graver相关的业务层操作:
@interface WMGBaseViewModel : NSObject{
...
@property (nonatomic, strong, readonly) NSMutableArray <WMGBaseCellData *> *arrayLayouts;//包装数据提供给cell
// 网络请求返回的错误
@property (nonatomic, strong, readonly) NSError *error;
// 当前列表状态,详见WMGListState说明
@property (nonatomic, assign) WMGListState listState;
// 松耦合方式连接engine和viewModel,通过桥接模式实现
@property (nonatomic, strong) WMGBaseEngine *engine;
数据最终包装成WMGBaseCellData
提供给cell
:
@interface WMGBaseCellData : NSObject
// cell宽度
@property (nonatomic, assign) CGFloat cellWidth;
// cell高度
@property (nonatomic, assign) CGFloat cellHeight;
// 视图分割线样式
@property (nonatomic, assign) WMGCellSeparatorLineStyle separatorStyle;
// 根据该属性值反射UI数据对应的视图Class,子类可以通过覆盖方式指定,默认取当前类同名对应的Cell
// 例如: WMGListCellData -> WMGListCell
@property (nonatomic, assign, readonly) Class cellClass;
// UI数据对应的业务数据
@property (nonatomic, weak) id <WMGClientData> metaData;
@end
还有WMGBaseEngine
,负责数据的请求、处理,需要继承重写相关方法:
/*
整体负责
1.数据存储 Insert Delete Update Select操作
2.网络请求、数据解析
*/
@interface WMGBaseEngine : NSObject{ ... }
// 结果集,业务列表数据、是否有下一页、当前处于第几页的封装,适用于流式列表结构
@property (nonatomic, strong, readonly, nullable) WMGResultSet *resultSet;
// 载入状态,用于标识当前网络请求的载入状态
@property (nonatomic, assign, readonly) WMGEngineLoadState loadState;
//reload请求
- (void)reloadDataWithParams:(nullable NSDictionary *)params completion:(nullable WMGEngineLoadCompletion)completion;
//loadmore请求
- (void)loadMoreDataWithParams:(nullable NSDictionary *)params completion:(nullable WMGEngineLoadCompletion)completion;
//insert请求
- (void)insertDataWithParams:(nullable NSDictionary *)params withIndex:(NSUInteger)insertIndex completion:(nullable WMGEngineLoadCompletion)completion;
@end
// 对数据的增删改查
@interface WMGBaseEngine (DataOperation)
//添加一条数据
- (void)addItem:(WMGBusinessModel *)item;
//插入一条数据
- (void)insertItem:(WMGBusinessModel *)item atIndex:(NSUInteger)index;
//删除一条数据
- (void)deleteItem:(WMGBusinessModel *)item;
//删除所有业务数据
- (void)deleteAllItems;
@end
简单来说,之前文章说的
WMGAsyncDrawView
负责UI
层,WMGBaseViewModel
负责业务层。
网友评论