简介

图片编辑是基于 CLImageEditor 第三方进行改装得到仿微信的图片编辑交互。原CLImageEditor库是一个日本人写的图片编辑,支持涂鸦,裁剪,旋转,文字帖功能。但有几项我们进行改进。

  • 所有的这些图片操作我们汇总到一起进行编辑,最终保存的时候才处理所有操作汇总成一张处理图片。
  • 涂鸦撤销功能实现真正的上一步撤销,而不是仅仅透明色进行橡皮擦擦除。
  • 新增马赛克操作功能。
  • 把图片裁剪和旋转进行结合到一个页面操作,另外新增裁剪、旋转之后的图片恢复。
  • 改变颜色选择,文字编辑的交互,新增图片裁剪区域的边角重点标注。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    CLImageEditorViewController

    usedToolDic:使用过的工具【toolName:CLImageToolBase

    -(CLImageToolBase *)currentEditTool; 当前正在使用的工具。

    -(UIImage *)getMergeDrawImageForCropTool; 最后合成的图片。用于业务转发、保存、回调给用户。

    //图片编辑器所支持的工具类都继承于CLImageToolBase, 通过CLClassList辅助类runtime方式自动将相关工具类注入到菜单项中。
    + (NSArray*)subtools
    {
    return [CLImageToolInfo toolsWithToolClass:[CLImageToolBase class]];
    }
    CLImageToolBase 工具基类:持有editor编辑器方便拿到图片,同时具有setup, cleanUp 这几个“生命周期”方法,做相关工具的视图创建与清除。

我们每个人的手机都安装有微信App,肯定大家都使用过以下场景在聊天群中,当有新的成员进入的时候,可以看到 某某邀请了某某1进入群聊,或者当你是群主的时候,某管理人员邀请了几个人进入时候看到 某某管理员邀请了张三、李四,王五,张三(同名)进入了群聊。对于邀请人员或者被邀请人员,我们发现人员的名称是可以点击的,即使同名的人,点击其名字也可以跳转到各自的主页详情中。

如何存储人员的id?

一条群成员变更通知就是一串文本,我们点击的时候肯定要存储好他的人员id,然后点击的时候就能拿到对应人员的id。从最简单的存储方案来看,这条内容可以写成以下格式: 管理员刘某某邀请了张三李四王五张三进入了群聊。换成代码的格式即为:“管理员\”刘某某::1000\”邀请了\”张三::1001、李四::1002、王五::1003、张三::1004\”进入了群聊”。其中的代码格式为userName::userId这样一种形式进行拼接,中间的符号可以采用其他特殊符号代替。这里为了区别用户特殊性输入用了双冒号。多个成员之间用顿号隔开,人员与普通内容之间用符号双引号\”进行分割(斜杆为转义符)。 我们需要对这一串文字表达式进行解析进行UI显示,把人员名字后的拼接符号用户id进行过滤掉,组合成不带用户id的易懂文字内容给到用户。

群插件的基础属性

群插件一般是用H5配置的网页小插件,然后和群构造一个入口。本地通过sqlite维护一个插件表,以及群插件关系表。插件信息升级一下可分为平台系统必须插件以及业务自定义插件。具体可由业务决定。
插件有其基本属性:名称,插件id,类型,默认排序值,跳转内容(本地模块名/h5链接地址),跳转类型:h5/本地模块 等等。
插件关系表: 群id,插件id,角标数量,排序值,是否显示,等等。

开发中由于系统版本以及sdk,第三方库等原因,都会影响成出现一些奇怪的问题。当时是引用YYTextview控件解决消息cell长文本的光标选择范围内容复制的小功能。由于YYTextview光标弹出层自定义在另外一个新的Windown上,然后左侧侧滑出的view是新增在另一个新的Windown中。当侧边栏dismiss的时候,可能由于windown层级的影响,当在聊天输入框再次输入文字长按时候,系统出现的UIMenu层的Button不见了。当时想从根本问题去排查为啥系统的UIMenu当长按的时候操作功能的button为啥不见去解决,花费了将近2个多小时通过Xcode调试查看层级,阅读YYTextview的光标自定义层的实现以及添加到windown的层级,查看左侧侧边栏添加动画实现方式等都未解决。然后想到如果不通过新创建window来添加view 实现动画,而是通过present一个控制器实现侧边栏动画。最终问题得到了解决。

旧Slide侧滑方案

旧有的侧滑方案是创建一个新window,然后设置其windowLevel 为正常的+1,这样就在最顶层了,然后设置其rootViewController为侧滑的控制器。很有可能是新创建的window与YYtextView的光标选择层创建的window有冲突,然后造成UIMenu按钮消失了。

改进方案,Present一个控制器 SlideVC

实则我们可以通过拿到最顶层控制器topVC,用导航控制器把SlideVC包裹好,设置它的 modalPresentationStyle 模式为UIModalPresentationOverCurrentContext,然后present出来 .这里要注意的是,系统的present方法是否有被runtime method swizled进行方法替换来公共统一处理modelPresentStyle问题,因为有些App交互不想使用系统的抽屉式present效果, 如果有需要做相关的过滤业务控制器逻辑判断。 侧边动画的实现:在SlideVC中定义一个半透明层背景bgView,添加tap手势,点击即关闭。往bgView添加一个左侧内容视图leftView 75%宽,右侧rightView。self.view添加一个Pan手势,用来计算leftView拖拽时候X的值位移变化。将内容控制器交由SlideVC管理:

2020 年下半年iOS工作技术点 tips

背景:疫情原因导致之前云喵工作不能够继续,2020年6月还是面试找了一份相对稳定的工作,去了南航的一家外包供应商。

E App 是一个企业内部OA即时通讯App. 驻场南航后主要是针对此项目进行功能迭代敏捷开发。主要说一下几个技术任务点:

  1. 首要任务就是将旧有MRC内存运行环境改成ARC运行环境。
    任务到不是复杂,主要修改点是多,体力劳动。将ARC相关的修饰关键词,控制器的dealloc 方法以及设置的代理delegate, 配置文件-fno-objc 相关配置干掉。
    遇到的问题?
    期间由于之前是MRC手动内存回收机制,由于开发人员代码某些变量没有回收或者泄露贮存在内存中,当改成ARC之后会自动释放导致再次使用该变量的时候出现nil值崩溃或者业务的中断开!
    C++代码块Client类某方法的变量被释放造成崩溃?
    当时问题点抛向如何停止某线程的解决方案上去了,而不是终止某条件然后让线程自然停止。当程序进入后台applicationWillTerminate的时候,手动退出IM.然后进入销毁连接阶段。为了使alive_thread , recv_thread 两线程退出,在CLIENT_Disconnect 销毁阶段,通过改变alivethread的条件让while条件中止,线程必然退出。
  2. h5网页打卡有时候定位不到或者崩溃。因App程序使用了百度地图sdk,所以直接升级百度地图sdk即可。sdk编译分xcode11.3 或 xcode 12 。具体看E项目如果采取xcode11.3 编译打包上线,那么需要找到适应的.a包或者framework 进行编译。
  3. 将ASI网络文件下载改成AFNetwroking网络文件下载,将长进度条改成扇形进度条。
    ASI那套网络请求比较老旧,当时还是用NSURLConnnection来进行网络请求,而且需要单独维护一个网络线程长期贮存到内存中。AFN 采取NSURLSession来进行网络请求,线程即用即回收。
    AFN请求时设置支持SSL安全策略。

    1
    2
    3
    4
    5
    AFSecurityPolicy *securityPolicy = [[AFSecurityPolicy alloc] init];
    securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeNone];
    securityPolicy.allowInvalidCertificates = YES;
    securityPolicy.validatesDomainName = NO;
    [_manager setSecurityPolicy:securityPolicy];

    然后通过NSURLSessionDownloadTask 进行下载request,返回对应的进度,通过KVO进行进度条的UI显示,也可通过Block传递到控制器进行显示。

业务思路: 客户端调用Java接口把消息id,收藏时间传给后台APIWeb服务器,后端和 IM 服务器通讯,拿到真正收藏的消息StoreBody 进行存储。

收藏:
APIWeb服务器业务收藏成功,返回相关收藏id给客户端。进行存储到表中。

删除:
客户端根据收藏id调用java接口成功后将收藏记录移除。

同步数据:
每次打开个人中心的收藏页面,请求接口进行数据同步,服务器存储时间戳和数条记录,客户端拿到数据后进行循环处理每条记录,根据收藏id,收藏时间,删除标记来决定是否将该记录进行插入到本地收藏表中。

为了使搜索的结果控制器直接重用收藏的控制器。封装了类似EHMyStoreBaseResultsController的result控制器来进行显示。 收藏的基类EHMyStoreBaseController 持有displayController 即为该类型。
同时,该StoreBaseController实现了GYContainerSearchResultsDelegate协议。如下:记录搜索关键字。

1
2
3
4
- (void)beginStoreSearchByKeyWords:(NSString *)searchStr
{
_historySearchStr = searchStr;
}
  • h 文件
1
2
3
4
5
6
7
8
9
@protocol GYContainerSearchResultsDelegate <NSObject>
//点击搜索,去查询数据库记录
- (void)beginStoreSearchByKeyWords:(NSString *)searchStr;

@end

@interface EHMyStoreBaseResultsController : GYContainerSearchResultsController
- (instancetype)initWithWrapStoreBaseControllerName:(NSString *)baseChildControllerName withOwerViewController:(EHMyStoreBaseController *)storeChildVC;
@end
  • m文件
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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
@interface EHMyStoreBaseResultsController()
@property(strong,nonatomic) EHMyStoreBaseController *childStoreVc; //EHMyStoreBaseController子类的搜索结果集展示类
@property(strong,nonatomic) EHMyStoreBaseController *owerStoreVc; //EHMyStoreBaseController的子类。
@end

@implementation EHMyStoreBaseResultsController

- (instancetype)initWithWrapStoreBaseControllerName:(NSString *)baseChildControllerName withOwerViewController:(EHMyStoreBaseController *)storeChildVC
{
if(self = [super initWithWrapStoreBaseControllerName:baseChildControllerName]) {

_owerStoreVc = storeChildVC;
}
return self;

}

-(void)viewDidLoad
{
[super viewDidLoad];

_childStoreVc = [[NSClassFromString(self.baseChildControllerName) alloc] init];
_childStoreVc.withoutSearchFlag = YES; //不需要搜索条
_childStoreVc.markSearchResultFlag = YES; //标记在搜索页: 需要把顶部收藏分类去掉
[self addChildViewController:_childStoreVc];

[self.view addSubview:_childStoreVc.view];
[_childStoreVc.view mas_remakeConstraints:^(MASConstraintMaker *make) {
make.left.right.bottom.mas_equalTo(0);
make.top.mas_equalTo((self.searchBar.frame.size.height + STATUSBAR_HEIGHT ));
}];
}


#pragma mark------UISearchBarDelegate-----
-(BOOL)searchBarShouldBeginEditing:(UISearchBar *)searchBar {
if([self.owerStoreVc respondsToSelector:@selector(cancelMulEditStatus)]){
[self.owerStoreVc cancelMulEditStatus];
}
return YES;
}

- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {
NSLog(@"%s", __FUNCTION__);
self.searchStr = [StringUtil trimString:searchBar.text];
[self setSearchResultsTitle:nil];//先不显示一下

if([self.searchStr length] == 0 || [self.searchStr length] == 1) {
[self setSearchResultsTitle:@"收藏搜索的空文字提示"];
}else {
[self setSearchResultsTitle:nil];
}
[[self class] cancelPreviousPerformRequestsWithTarget:self selector:@selector(searchHandle) object:nil];
[self performSelector:@selector(searchHandle) withObject:nil afterDelay:0.2f];
}

//真正触发搜索的这一方法
- (void)searchHandle {
[super searchHandle];
if([self.childStoreVc respondsToSelector:@selector(beginStoreSearchByKeyWords:)]){
[self.childStoreVc beginStoreSearchByKeyWords:self.searchStr];
}
}

#pragma mark - UISearchResultsUpdating
//每输入一个字符都会执行一次
- (void)updateSearchResultsForSearchController:(UISearchController *)searchController{
// NSLog(@"搜索关键字:%@",searchController.searchBar.text);
//放在presentSearchController不生效,因此暂定放在此处
}

// 当用户在搜索控制器中开始编辑时或者将active属性设置为YES时,该方法被调用
- (void)presentSearchController:(UISearchController *)searchController{
[self setSearchResultsTitle:@"收藏搜索的空文字提示"];//设置一开始的提示语
self.searchStr = [StringUtil trimString:self.searchBar.text];
//清空搜索表格的数据

}

@end

分割线

EHMyStoreBaseResultsController: GYContainerSearchResultsController

  • GYSearchController 文件

    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
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    @interface GYSearchController : UISearchController
    @end

    #define SCREEN_MAX_LENGTH (MAX(SCREEN_WIDTH, SCREEN_HEIGHT))
    #define kIS_IPHONE_X (IS_IPHONE && SCREEN_MAX_LENGTH == 812.0)

    @interface GYSearchController ()
    @end

    @implementation GYSearchController

    - (void)viewDidLoad {
    NSLog(@"viewDidLoad");
    [super viewDidLoad];
    [self add_imageBg];
    }

    - (void)viewWillAppear:(BOOL)animated {
    NSLog(@"");
    [super viewWillAppear:animated];
    }

    #pragma mark -
    -(void)viewDidLayoutSubviews {
    NSLog(@"viewDidLayoutSubviews");
    [self setSomeFrame];
    [super viewDidLayoutSubviews];
    [self setSomeFrame];
    }

    -(void)viewWillLayoutSubviews {
    NSLog(@"viewWillLayoutSubviews");
    [self setSomeFrame];
    [super viewWillLayoutSubviews];
    [self setSomeFrame];
    }

    - (void) setSomeFrame {
    if(kIS_IPHONE_X){
    if(1){//searBar内部背景修正
    if (@available(iOS 11.0, *)) {
    CGRect rect = [[self.searchBar subviews][0] subviews][0].frame;
    if(self.isActive){
    CGFloat statusHeight = [[UIApplication sharedApplication] statusBarFrame].size.height;
    rect.origin.y = - statusHeight;
    rect.size.height = self.searchBar.bounds.size.height;
    [[self.searchBar subviews][0] subviews][0].frame = rect;
    }
    }
    }
    }
    }

    //添加图片的方式
    - (void)add_imageBg {
    UIImageView *imageBg = ({
    UIImageView *iv = (UIImageView*)[self.view viewWithTag:999];
    if(!iv){
    UIColor *searchBarBgColor = [UIColor colorWithRed:242.0/255.0 green:242.0/255.0 blue:242.0/255.0 alpha:1.0];
    CGFloat statusHeight = [[UIApplication sharedApplication] statusBarFrame].size.height;
    iv = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, statusHeight+self.searchBar.frame.size.height-1)];//后面-1是因为iphoneX实际调试为99=44+56-1
    iv.backgroundColor = [UIColor redColor];
    iv.backgroundColor = searchBarBgColor;
    iv.tag = 999;
    [self.view addSubview:iv];
    }
    iv;
    });
    imageBg.hidden = NO;
    }

    @end
  • GYContainerSearchResultsController 文件

  • h文件

    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
    #define IS_IOS11   ([[[UIDevice currentDevice]systemVersion]floatValue] >= 11.0)?YES:NO //是否iOS11及以后
    #define kSearchBarHeight ((IS_IOS11)? 56.0f:44.0f)

    ///为UISearchController的searchResultsController对象
    @interface GYContainerSearchResultsController : UIViewController

    @property(nonatomic,readonly) NSString *baseChildControllerName;

    - (instancetype)initWithWrapStoreBaseControllerName:(NSString *)baseChildControllerName;

    /** 搜索的searchController */
    @property(nonatomic, strong) UISearchController *searchController;
    /** 是否激活搜索状态中:注意新旧两版不同细节情况;特别旧版在WillEnd开始就为NO了 */
    @property(nonatomic, assign) BOOL isActive;
    /** 搜索的searchBar */
    @property(nonatomic, strong) UISearchBar *searchBar;
    /** 搜索的搜索字串 */
    @property (nonatomic,strong) NSString *searchStr;
    /** 当前Search的上级宿主viewController (一般为self.searchController的nextResponder,只有在搜索框显示出来时才可使用!!!) */
    @property(nonatomic, weak) UIViewController *hostViewController;

    @property(nonatomic, strong) NSString *defaultTipStr;
    #pragma mark - UISearchControllerDelegate的几个回调block========================
    @property(nonatomic, copy) void(^willPresentSearchControllerBlock)(GYContainerSearchResultsController *searchResultsController);
    @property(nonatomic, copy) void(^didPresentSearchControllerBlock)(GYContainerSearchResultsController *searchResultsController);
    @property(nonatomic, copy) void(^willDismissSearchControllerBlock)(GYContainerSearchResultsController *searchResultsController);
    @property(nonatomic, copy) void(^didDismissSearchControllerBlock)(GYContainerSearchResultsController *searchResultsController);

    #pragma mark - UISearchController辅助初始化方法===================================
    /** 上级viewController初始化一些设置 @param viewController 传入待处理的vc */
    + (void)viewController_viewDidLoad_someSetting:(UIViewController*)viewController;
    /** 初始化设置searchBar */
    - (void)initSearchBar;

    -(void)viewController_viewDidLayoutSubviews:(UITableView *)tableView hostViewController:(UIViewController*)hostViewController bottomMargin:(CGFloat)bottomMargin;
    @end

    #pragma mark - UISearchBarDelegate-暴露出子类可以重写的方法名======================
    @interface GYContainerSearchResultsController(GY_UISearchBarDelegate)
    -(BOOL)searchBarShouldBeginEditing:(UISearchBar *)searchBar;
    - (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText;
    - (void)searchHandle;
    - (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar;
    - (void)setSearchResultsTitle:(NSString *)title;
    - (void)showSearchTip:(NSString *)title;
    @end
  • m文件

    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
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    #define SCREEN_MAX_LENGTH (MAX(SCREEN_WIDTH, SCREEN_HEIGHT))
    #define kIS_IPHONE_X (IS_IPHONE && SCREEN_MAX_LENGTH == 812.0)

    @interface GYContainerSearchResultsController ()<UISearchResultsUpdating, UISearchControllerDelegate, UISearchBarDelegate
    >
    @property (nonatomic, strong) UILabel *emptyTipsLabel;
    @property (nonatomic, strong) UILabel *defaultTipsLabel;
    @end

    @implementation GYContainerSearchResultsController

    - (UIViewController *)hostViewController {
    if (_hostViewController==nil) {
    UIResponder *nextResponder = [self.searchController nextResponder];
    if ([nextResponder isKindOfClass:[UIViewController class]]) {
    _hostViewController = (UIViewController *)nextResponder;
    }
    }
    return _hostViewController;
    }

    - (BOOL)isActive {
    return self.searchController.active;
    }

    - (instancetype)initWithWrapStoreBaseControllerName:(NSString *)baseChildControllerName
    {
    if(self = [super init]) {

    _baseChildControllerName = baseChildControllerName;
    //初始化设置searchBar
    [self initSearchBar];
    }
    return self;
    }

    - (void)viewDidLoad {
    [super viewDidLoad];

    if(_emptyTipsLabel == nil){
    _emptyTipsLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 30, SCREEN_WIDTH, 30)];
    _emptyTipsLabel.font = [UIFont systemFontOfSize:20.0f];
    _emptyTipsLabel.textColor = [UIColor colorWithRed:202.0/255.0 green:202.0/255.0 blue:202.0/255.0 alpha:1.0];
    _emptyTipsLabel.textAlignment = NSTextAlignmentCenter;
    [self.view addSubview:_emptyTipsLabel];
    }
    }

    - (UILabel *)defaultTipsLabel{
    if (_defaultTipsLabel == nil) {
    UILabel *lab = [UILabel new];
    lab.textColor = KUIColorFromRGB(0x417FF8);
    lab.font = [UIFont systemFontOfSize:17];
    _defaultTipsLabel = lab;
    _defaultTipsLabel.hidden = YES;
    [self.searchController.searchBar addSubview:_defaultTipsLabel];
    }
    return _defaultTipsLabel;
    }

    #pragma mark - UISearchResultsUpdating
    - (void)updateSearchResultsForSearchController:(UISearchController *)searchController {
    NSLog(@"updateSearchResultsForSearchController->inputStr==%@",searchController.searchBar.text);
    }


    #pragma mark - UISearchControllerDelegate代理
    - (void)willPresentSearchController:(UISearchController *)searchController {
    NSLog(@"willPresentSearchController");
    if (self.defaultTipStr.length) {
    self.defaultTipsLabel.hidden = NO;
    float w = [self.defaultTipStr boundingRectWithSize:CGSizeMake(MAXFLOAT, 30) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:[UIFont boldSystemFontOfSize:17]} context:nil].size.width + 10;
    self.defaultTipsLabel.text = self.defaultTipStr;
    self.defaultTipsLabel.frame = CGRectMake(40, 5, w, self.searchBar.bounds.size.height - 10);
    self.searchBar.searchTextPositionAdjustment = UIOffsetMake(w, 0);
    self.searchBar.placeholder = @"";
    }else{
    self.defaultTipsLabel.hidden = YES;
    self.searchBar.searchTextPositionAdjustment = UIOffsetMake(0, 0);
    [self.searchBar changeLeftPlaceholder:[StringUtil getLocalizableString:@"chats_search"]];
    }
    !self.willPresentSearchControllerBlock?:self.willPresentSearchControllerBlock(self);
    }

    - (void)didPresentSearchController:(UISearchController *)searchController {
    NSLog(@"didPresentSearchController");
    !self.didPresentSearchControllerBlock?:self.didPresentSearchControllerBlock(self);
    }

    - (void)willDismissSearchController:(UISearchController *)searchController {

    [self.navigationController setNavigationBarHidden:NO animated:NO];
    [self.navigationController.view layoutIfNeeded];
    self.defaultTipsLabel.hidden = YES;
    self.searchBar.searchTextPositionAdjustment = UIOffsetMake(0, 0);
    [self.searchBar changeLeftPlaceholder:[StringUtil getLocalizableString:@"chats_search"]];
    !self.willDismissSearchControllerBlock?:self.willDismissSearchControllerBlock(self);
    }

    - (void)didDismissSearchController:(UISearchController *)searchController {
    NSLog(@"didDismissSearchController");
    !self.didDismissSearchControllerBlock?:self.didDismissSearchControllerBlock(self);
    }

    - (void)presentSearchController:(UISearchController *)searchController {
    NSLog(@"presentSearchController");
    }


    #pragma mark------UISearchBarDelegate-----
    -(BOOL)searchBarShouldBeginEditing:(UISearchBar *)searchBar {
    NSLog(@"searchBarShouldBeginEditing");
    return YES;
    }

    - (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {
    NSLog(@"%s", __FUNCTION__);
    self.searchStr = [StringUtil trimString:searchBar.text];
    [self setSearchResultsTitle:nil];

    if([self.searchStr length] == 0) {
    }
    else if ([self.searchStr length] == 1) {
    [self setSearchResultsTitle:[StringUtil getLocalizableString:@"search_tip"]];
    }
    else {
    [[self class] cancelPreviousPerformRequestsWithTarget:self selector:@selector(searchHandle) object:nil];
    [self performSelector:@selector(searchHandle) withObject:nil afterDelay:0.2f];
    }
    }

    - (void)searchHandle {
    NSLog(@"开始去搜索。。。。");
    }

    - (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar {
    if ([self.searchStr length] < 1) {
    return;
    }
    //去除焦点收键盘
    [searchBar resignFirstResponder];
    //搜索提示
    [[LCLLoadingView currentIndicator] setCenterMessage:[StringUtil getLocalizableString:@"searching"]];
    [[LCLLoadingView currentIndicator] show];
    //再搜索
    [self searchHandle];
    }

    //搜索提示文字
    - (void)setSearchResultsTitle:(NSString *)title {
    if(title==nil){
    _emptyTipsLabel.hidden = YES;
    }else{
    _emptyTipsLabel.hidden = NO;
    _emptyTipsLabel.text = [NSString stringWithFormat:@"%@", title];
    }
    }

    //alert
    - (void)showSearchTip:(NSString *)title {
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:title message:@"" delegate:nil cancelButtonTitle:[StringUtil getLocalizableString:@"confirm"] otherButtonTitles: nil];
    [alert show];
    }


    #pragma mark - UISearchController辅助初始化方法===================================
    #pragma mark - 上级viewController初始化一些设置
    + (void)viewController_viewDidLoad_someSetting:(UIViewController*)viewController
    {
    UIViewController *_self = viewController;
    //#warning 如果进入预编辑状态,searchBar消失(UISearchController套到�TabBarController可能会出现这个情况),请添加下边这句话
    _self.definesPresentationContext=YES;
    if (@available(iOS 11.0, *)) {
    } else {
    _self.automaticallyAdjustsScrollViewInsets = NO;
    }

    //适配ios7UIViewController的变化
    if ([_self respondsToSelector:@selector(extendedLayoutIncludesOpaqueBars)]) {
    _self.extendedLayoutIncludesOpaqueBars = NO;
    }
    _self.edgesForExtendedLayout = UIRectEdgeAll;

    //第三种方式开关
    [_self setAutomaticallyAdjustsScrollViewInsets:YES];
    [_self setExtendedLayoutIncludesOpaqueBars:YES];
    _self.edgesForExtendedLayout = UIRectEdgeNone;
    _self.edgesForExtendedLayout = UIRectEdgeBottom | UIRectEdgeLeft | UIRectEdgeRight;
    }

    #pragma mark - 初始化设置searchBar
    - (void)initSearchBar {
    // 创建用于展示搜索结果的控制器
    GYContainerSearchResultsController *result = self;
    self.searchController = ({
    UISearchController *_searchController = [[GYSearchController alloc] initWithSearchResultsController:result];
    _searchController.delegate = self;
    _searchController.searchResultsUpdater = result;
    _searchController.dimsBackgroundDuringPresentation = YES;//取消蒙版
    _searchController.obscuresBackgroundDuringPresentation = NO;//搜索时,背景变模糊
    _searchController.hidesNavigationBarDuringPresentation = YES;//点击搜索的时候,是否隐藏导航栏
    _searchController;
    });
    self.searchBar = self.searchController.searchBar;
    [self.searchBar changeLeftPlaceholder:[StringUtil getLocalizableString:@"chats_search"]];
    self.searchBar.delegate = self;

    [self.searchBar setBackgroundImage:[self imageWithColor:KUIColorFromRGB(0xF5F5F5) size:self.searchBar.bounds.size]];
    UIImage *image = [self imageWithColor:KUIColorFromRGB(0xffffff) size:CGSizeMake(self.searchBar.bounds.size.width, self.searchBar.bounds.size.height - 20)];
    image = [self rh_bezierPathClip:image cornerRadius:8];
    [_searchBar setSearchFieldBackgroundImage:image forState:UIControlStateNormal];
    }

    - (UIImage *)rh_bezierPathClip:(UIImage *)img
    cornerRadius:(CGFloat)cornerRadius {
    int w = img.size.width * img.scale;
    int h = img.size.height * img.scale;
    CGRect rect = (CGRect){CGPointZero, CGSizeMake(w, h)};
    UIGraphicsBeginImageContextWithOptions(CGSizeMake(w, h), false, 1.0);
    [[UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:cornerRadius] addClip];
    [img drawInRect:rect];
    UIImage *cornerImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return cornerImage;
    }

    - (UIImage *)imageWithColor:(UIColor *)color size:(CGSize)size{
    CGRect rect = CGRectMake(0, 0, size.width, size.height);
    UIGraphicsBeginImageContext(rect.size);
    CGContextRef context = UIGraphicsGetCurrentContext();

    CGContextSetFillColorWithColor(context, [color CGColor]);
    CGContextFillRect(context, rect);

    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    return image;
    }

    #pragma mark -上级viewController辅助方法

    -(void)viewController_viewDidLayoutSubviews:(UITableView *)tableView hostViewController:(UIViewController*)hostViewController bottomMargin:(CGFloat)bottomMargin{
    //调节宿主tableView
    CGRect tableViewAdjustRect = CGRectZero;
    if(self.isActive) {
    CGFloat y = [[UIApplication sharedApplication] statusBarFrame].size.height+self.searchBar.frame.size.height;
    y = (!kIS_IPHONE_X)?y:(y-1);//iphonex时搜索部分的高度为99=44+56-1;
    CGFloat height = SCREEN_HEIGHT-y;
    UINavigationController *nav = hostViewController.navigationController;
    if(nav && nav.viewControllers[0] == hostViewController
    && [nav nextResponder]
    &&([[nav nextResponder] isKindOfClass:[UITabBarController class]]//ios11
    ||[[[[nav nextResponder] nextResponder] nextResponder] nextResponder]//ios10
    )
    ){//在tabarController里且为其导航根vc时,要减去下面的tabBar下面高度
    height = SCREEN_HEIGHT-y-TABBAR_HEIGHT-safeAreaXBottom;
    }
    tableViewAdjustRect = CGRectMake(0, y, SCREEN_WIDTH, height);
    }else {
    CGFloat y = self.searchBar.frame.size.height;
    CGFloat height = SCREEN_HEIGHT-NAVIGATIONBAR_HEIGHT-STATUSBAR_HEIGHT-y;//-safeAreaXBottom;
    UINavigationController *nav = hostViewController.navigationController;
    if(nav && nav.viewControllers[0] == hostViewController
    && [nav nextResponder]
    &&([[nav nextResponder] isKindOfClass:[UITabBarController class]]//ios11
    ||[[[[nav nextResponder] nextResponder] nextResponder] nextResponder]//ios10
    )
    ){
    height = SCREEN_HEIGHT-NAVIGATIONBAR_HEIGHT-STATUSBAR_HEIGHT-self.searchBar.frame.size.height-TABBAR_HEIGHT-safeAreaXBottom;
    }
    tableViewAdjustRect = CGRectMake(0, y, SCREEN_WIDTH, height);
    }
    if (bottomMargin > 0) {
    CGRect rect = tableViewAdjustRect;
    CGFloat h = rect.size.height - bottomMargin;
    if ((UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) && ([UIScreen mainScreen].bounds.size.width >= 375) && ([UIScreen mainScreen].bounds.size.height >= 812)) {
    h -= 34;
    }
    tableView.frame = CGRectMake(rect.origin.x, rect.origin.y, rect.size.width,h);
    }else{
    tableView.frame = tableViewAdjustRect;
    }
    }

使用TZImagePicker,添加预览。支持视频、图片、gif。 当浏览大图时候,需要进行手势控制。 视频,图片,gif 继承原有的cell。
在PhotoPreviewController配置手势处理configDragContainerView。当往下拖拽时候消失控制器。当滑动到视频时候,显示一个关闭按钮。手势处理只需定义一个NHPanGestureRecognizer继承UIPanGestureRecognizer,通过touchesMoved控制state UIGestureRecognizerStateFailed。

旋转: 先保存原图,然后用原图来预览旋转后的效果。

聊天输入框@某人逻辑分类点说明

IM 应用软件无论是App端或者是PC端都有@某人这样一个功能点,由于用户输入框输入条件的复杂随机性种类多,@的针对不同情况进行拼接或者新增样式显示蓝色字体表示该人名可链接。由输入框开始,用户输入@的方式进行分类:

  1. 首字母为空,用户直接输入@,然后跳转到成员页面进行选择相关人员,选完即带回人名 结果为:“@某人”
  2. input输入框输入了文字的基础上: 在最前面下班为0的第1个位置进行插入@ 结果为:“@某人原input输入框的文字”
  3. input输入框输入了文字的基础上,在最后面下班为length-1的第后一个位置进行插入@ 结果为:“原input输入框的文字@某人”
  4. input输入框输入了文字的基础上,在文字内容中输入@。即条件 0 < index < length-1 结果为:“原input输入@某人框的文字”

在以上四种输入情况中,3,4 输入拼接@某人的时候,需要判断前一个字是否为@, 如果是,则后面拼接的时候直接+某人即可。另外当在第4种情况文字中间输入的时候,中英文下输入可能遇到文本框的值为 “ @@文字”,按照之前的逻辑当光标输入在第2个@后时,然后键盘输入@跳转页面选择成员,最后的结果应该是“ @@某人文字”这个组合,为了更友好地显示,还需检查当前两个也是@的时候,即出现1个@. 即结果为“ @某人文字” 以便更友好地显示。

核心逻辑:根据文本框原输入文字是否有内容,无内容时候直接拼接。有内容时候分两种情况,即光标的输入位置情况:当location等于0的时候,即在首位置输入@某人拼接原文本内容。当location大于0的时候,即光标可能出现在内容文字之间,也可能是在最后面。也是分两种情况:根据location+1判断是否小于原文本框文字长度length.如果小于,则表示在内容之间插入。否则,在内容最后尾部插入。无论是之间还是尾部做插入,都需检查前一个字符是否为@,如果是,则在做拼接人名时候就无需再追加@。当在之间做插入的时候,为了友好显示,可适当判断前两个字符为@@时候进行替换为一个@。

Echart

数据看板首要问题,就是我们要解决数据分组的问题。你可以存储到浏览器的websql中通过sql语句groupBy来进行分组统计,或者你自己用js对数据进行分组。由于数据量有限,也就几百个吧,这里采取js方式对数据进行内存分组统计。

1
2
3
4
5
6
7
8
9
export const groupBy = (list, fn) => {
const groups = {};
list.forEach(function(o) {
const group = JSON.stringify(fn(o));
groups[group] = groups[group] || [];
groups[group].push(o);
});
return groups;
}