feat(sos): 集成微信SDK,实现地图卡片长按转发微信;新增急救资源详情页及群成员面板

- 手动集成微信 OpenSDK-NoPay,创建 XJWeChatManager 统一管理分享能力
- 地图卡片新增长按手势,通过代理模式实现「转发到微信」,分享含高德导航链接
- 聊天页新增群成员侧滑面板,支持查看成员列表及一键拨号
- 新增急救资源详情页(XJFirstAidResourceDetailVC)及选择联系人页
- 首页健康知识/资讯模块,含双视图列表及 H5 跳转
- 修复急救资源详情页编译报错

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
jiayangyang
2026-07-14 17:53:44 +08:00
co-authored by Claude Opus 4.7
parent 6337454956
commit ee498064cc
87 changed files with 7547 additions and 263 deletions
@@ -7,6 +7,8 @@
objects = {
/* Begin PBXBuildFile section */
188C3FFF3005E07600461C07 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 188C3FFE3005E07600461C07 /* Security.framework */; };
188C40013005E08000461C07 /* CoreTelephony.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 188C40003005E08000461C07 /* CoreTelephony.framework */; };
2A9D6F4901C83B23A09A207B /* libPods-XinjiangProject.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5380821CF956D8001EF91CA7 /* libPods-XinjiangProject.a */; };
/* End PBXBuildFile section */
@@ -31,6 +33,10 @@
1809E4622E092C710034C8FC /* XinjiangProject.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = XinjiangProject.app; sourceTree = BUILT_PRODUCTS_DIR; };
1809E47D2E092C740034C8FC /* XinjiangProjectTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = XinjiangProjectTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
1809E4872E092C740034C8FC /* XinjiangProjectUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = XinjiangProjectUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
188C3FFE3005E07600461C07 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; };
188C40003005E08000461C07 /* CoreTelephony.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreTelephony.framework; path = System/Library/Frameworks/CoreTelephony.framework; sourceTree = SDKROOT; };
188C40023005E08B00461C07 /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; };
188C40033005E09100461C07 /* libc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; };
5380821CF956D8001EF91CA7 /* libPods-XinjiangProject.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-XinjiangProject.a"; sourceTree = BUILT_PRODUCTS_DIR; };
D5F062871D7E5499A760A0A9 /* Pods-XinjiangProject.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-XinjiangProject.release.xcconfig"; path = "Target Support Files/Pods-XinjiangProject/Pods-XinjiangProject.release.xcconfig"; sourceTree = "<group>"; };
F4F935FBD1765B37B37D212D /* Pods-XinjiangProject.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-XinjiangProject.debug.xcconfig"; path = "Target Support Files/Pods-XinjiangProject/Pods-XinjiangProject.debug.xcconfig"; sourceTree = "<group>"; };
@@ -72,6 +78,8 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
188C40013005E08000461C07 /* CoreTelephony.framework in Frameworks */,
188C3FFF3005E07600461C07 /* Security.framework in Frameworks */,
2A9D6F4901C83B23A09A207B /* libPods-XinjiangProject.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -118,6 +126,10 @@
5E0194FB12246C95B3FA5215 /* Frameworks */ = {
isa = PBXGroup;
children = (
188C40033005E09100461C07 /* libc++.tbd */,
188C40023005E08B00461C07 /* libz.tbd */,
188C40003005E08000461C07 /* CoreTelephony.framework */,
188C3FFE3005E07600461C07 /* Security.framework */,
5380821CF956D8001EF91CA7 /* libPods-XinjiangProject.a */,
);
name = Frameworks;
@@ -374,8 +386,8 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = XinjiangProject/XinjiangProject.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2;
DEVELOPMENT_TEAM = A7CC5LW224;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_PREFIX_HEADER = "$(SRCROOT)/XinjiangProject/Supporting Files/PrefixHeader.pch";
@@ -400,7 +412,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.13;
MARKETING_VERSION = 1.0.14;
PRODUCT_BUNDLE_IDENTIFIER = com.questionTools.XinjiangProject;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
@@ -418,8 +430,8 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = XinjiangProject/XinjiangProject.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2;
DEVELOPMENT_TEAM = A7CC5LW224;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_PREFIX_HEADER = "$(SRCROOT)/XinjiangProject/Supporting Files/PrefixHeader.pch";
@@ -444,7 +456,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.13;
MARKETING_VERSION = 1.0.14;
PRODUCT_BUNDLE_IDENTIFIER = com.questionTools.XinjiangProject;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
@@ -63,11 +63,11 @@
//正式地址
#define Http @"https://api-jkglpt.iosp.ydpt.tech"//
#define H5DevHost @"api-jkglpt.iosp.ydpt.tech"//
#define IntervenH5 @"https://console-jkglpt.iosp.ydpt.tech"
#define SCHEME @"https" //
#define OtherHttp @"https://api-jkglpt.iosp.ydpt.tech/hb" //
//#define Http @"https://api-jkglpt.iosp.ydpt.tech"//
//#define H5DevHost @"api-jkglpt.iosp.ydpt.tech"//
//#define IntervenH5 @"https://console-jkglpt.iosp.ydpt.tech"
//#define SCHEME @"https" //
//#define OtherHttp @"https://api-jkglpt.iosp.ydpt.tech/hb" //
@@ -75,11 +75,11 @@
//#define Http @"http://192.168.1.223"//彭杰
// 最新地址
//#define Http @"http://192.168.1.47"//王昊
//#define Http @"https://xjxc-api.mcrm.vip:8888"//
//#define H5DevHost @"xjxc-api.mcrm.vip:8888"//
//#define IntervenH5 @"https://xj-admin.mcrm.vip:8888"
//#define SCHEME @"https" //
//#define OtherHttp @"https://gstest.superwx.cn/xj_health" //
#define Http @"https://xj-api.yixiong-tech.com:8081"//
#define H5DevHost @"xjxc-api.mcrm.vip:8888"//
#define IntervenH5 @"http://10.10.10.228:23006/"
#define SCHEME @"https" //
#define OtherHttp @"https://gstest.superwx.cn/xj_health" //
#pragma mark - ——————— 健康知识/资讯 ————————
@@ -105,5 +105,8 @@
#define kHealthKnowledgeCategoryPath @"/cms/frontend/category/list"
#define kHealthKnowledgeArticleListPath @"/cms/frontend/articleList"
// 急救宣教资源
#define kFirstAidResourceDetailPath @"/health-emergency/api/emergency/firstAid/resource/resource/queryById"
#endif /* StatusMacros_h */
@@ -10,9 +10,11 @@
#define ThirdMacros_h
//微信
#define kAppKey_Wechat @""
#define kAppKey_Wechat @"wxb33cd60beda0a212"
#define kSecret_Wechat @""
#define kSecret_Wechat @"9de1222d6c32af184538d2ae25d1f5a2"
#define kUniversalLink_Wechat @"https://www.rk-health.com"
#define UMShareAppKey @""
@@ -22,6 +24,7 @@
#define buglyAppid @""
#define SecretKey @"XJp3EHpcnwReXc1A"
//高德key
@@ -55,4 +55,7 @@ typedef void (^FileDownloadFail)(int code, NSString * desc);
#pragma mark - 表单带参数
- (void)httpPostFormDataRequest:(NSString *)api params:(NSMutableDictionary *)params;
//外部接口
- (void)httpOtherPostRequest:(NSString *)api params:(NSMutableDictionary *)params;
@end
@@ -8,7 +8,7 @@
//
#import "JKCQNetworkingWithCache.h"
#import <CommonCrypto/CommonHMAC.h>
#define DYLog(...) NSLog(__VA_ARGS__) //如果不需要打印数据, 注释掉NSLog
@@ -20,8 +20,8 @@ typedef NS_ENUM(NSInteger, RequestType) {
RequestTypeMultiUpload,//多个上传
RequestTypeDownload,
RequestTypeUpLoadVideo,//上传视频
RequestTypeParmFormData//参数表单
RequestTypeParmFormData,//参数表单
RequestTypeOterTypePost//外部接口
};
@implementation JKCQNetworkingWithCache
@@ -65,6 +65,11 @@ typedef NS_ENUM(NSInteger, RequestType) {
[self httpRequestWithUrlStr:api params:params requestType:RequestTypePost isCache:YES cacheKey:api imageKey:nil withData:nil withDataArray:nil];
}
- (void)httpOtherPostRequest:(NSString *)api params:(NSMutableDictionary *)params
{
[self httpRequestWithUrlStr:api params:params requestType:RequestTypeOterTypePost isCache:NO cacheKey:nil imageKey:nil withData:nil withDataArray:nil];
}
#pragma mark - 上传文件方法
//上传单张图片
- (void)upLoadDataWithUrlStr:(NSString *)api params:(NSMutableDictionary *)params imageKey:(NSString *)name withData:(NSData *)data
@@ -372,8 +377,63 @@ typedef NS_ENUM(NSInteger, RequestType) {
[MBProgressHUD hideHUDForView:keyWindow animated:YES];
[EasyTextView showErrorText:@"请求超时"];
}
}];
}else if(requestType == RequestTypeOterTypePost) {//参数表单上传
//获取当前时间戳
NSDate *date = [NSDate date];
NSTimeInterval time = [date timeIntervalSince1970];
NSString *timeString = [NSString stringWithFormat:@"%.0f", time];
[session.requestSerializer setValue:@"application/json;charset=utf-8" forHTTPHeaderField:@"Content-Type"];
//加密
NSString * needSign = [NSString stringWithFormat:@"%@%@",timeString,params];
NSString * sign = [self signWithTimestamp:timeString body:params secretKey:SecretKey];
DLog(@"%@",sign);
[session POST:url parameters:params headers:@{@"X-Timestamp":timeString,@"X-Sign":sign} progress:^(NSProgress * _Nonnull downloadProgress) {
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[weakSelf dealWithResponseObject:responseObject cacheUrl:allUrl];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSString * statusCode = [error.userInfo objectForKey:@"NSLocalizedDescription"];
if ([statusCode containsString:@"401"]) {
DLog(@"%@",error.userInfo);
[HQCommonUtils keyedArchiverWithData:nil key:LoginUserInfo];
KPostNotification(KNotificationLoginStateChange, @NO);
if (![HQCommonUtils isLogin]) {
[EasyTextView showErrorText:@"请您登录"];
}
else{
[EasyTextView showErrorText:@"身份信息已过期"];
}
}
else
{
NSLog(@"%@",url);
[MBProgressHUD hideHUDForView:[HQCommonUtils getCurrentVC].view animated:YES];
UIWindow *keyWindow = UIApplication.sharedApplication.windows.firstObject;
for (UIWindow *window in UIApplication.sharedApplication.windows) {
if (window.isKeyWindow) {
keyWindow = window;
break;
}
}
[MBProgressHUD hideHUDForView:keyWindow animated:YES];
[EasyTextView showErrorText:@"请求超时"];
}
}];
}
}
@@ -582,5 +642,38 @@ typedef NS_ENUM(NSInteger, RequestType) {
self.requestDelegate=nil;
}
#pragma mark:外部加密sign
- (NSString *)signWithTimestamp:(NSString *)timestamp
body:(NSDictionary *)bodyDict
secretKey:(NSString *)secretKey {
// 1 字典转 JSON
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:bodyDict options:0 error:nil];
NSString *body = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
// 2 拼接字符串
NSString *signString = [NSString stringWithFormat:@"%@%@", timestamp, body];
// 3 HMAC-SHA256
const char *key = [secretKey UTF8String];
const char *data = [signString UTF8String];
unsigned char result[CC_SHA256_DIGEST_LENGTH];
CCHmac(kCCHmacAlgSHA256,
key,
strlen(key),
data,
strlen(data),
result);
// 4 转16进制
NSMutableString *hash = [NSMutableString string];
for (int i = 0; i < CC_SHA256_DIGEST_LENGTH; i++) {
[hash appendFormat:@"%02x", result[i]];
}
return hash;
}
@end
@@ -27,6 +27,22 @@
- (void)GetSoSHomePageResources:(NSMutableDictionary *)pamraDic;
- (void)PostEndSoSIMQuestion:(NSMutableDictionary *)pamraDic;
- (void)getSoSCarInfoLocationHome:(NSMutableDictionary *)parmDic;
// 急救员 - 搜索专家
- (void)getSearchExpert:(NSMutableDictionary *)parmDic;
// 急救员 - 搜索员工
- (void)getSearchEmployee:(NSMutableDictionary *)parmDic;
// 急救员 - 创建群聊(不发起音视频通话)
- (void)getCreateGroupMagFirstAider:(NSMutableDictionary *)parmDic;
// 急救员 - 邀请进群
- (void)postAddGroupUser:(NSMutableDictionary *)parmDic;
// 急救员 - 移除群成员
- (void)postRemoveGroupUser:(NSMutableDictionary *)parmDic;
// 急救员 - 群成员列表
- (void)getGroupMemberList:(NSMutableDictionary *)parmDic;
// 急救员 - 实际群成员列表(经腾讯IM验证,含isSelf字段)
- (void)getActualGroupMemberList:(NSMutableDictionary *)parmDic;
// 急救员 - 宣教资源详情
- (void)getFirstAidResourceDetail:(NSMutableDictionary *)parmDic;
//首页接口
@@ -186,4 +202,6 @@
//解绑手表
- (void)getUnBindWatchDevice:(NSMutableDictionary *)parmDic;
- (void)postPointsUnmberData:(NSMutableDictionary *)parmDic;
@end
@@ -64,11 +64,51 @@
- (void)getSoSCarInfoLocationHome:(NSMutableDictionary *)parmDic {
[httpRequest httpGetRequest:[NSString stringWithFormat:@"%@/medical-center/api/ambulance/nearbyAmbulance",Http] params:parmDic];
}
// 急救员 - 搜索专家
- (void)getSearchExpert:(NSMutableDictionary *)parmDic {
[httpRequest httpGetRequest:[NSString stringWithFormat:@"%@/health-emergency/api/emergency/order/searchExpert",Http] params:parmDic];
}
// 急救员 - 创建群聊(不发起音视频通话)
- (void)getCreateGroupMagFirstAider:(NSMutableDictionary *)parmDic {
[httpRequest httpGetRequest:[NSString stringWithFormat:@"%@/health-emergency/api/emergency/createGroupMagFirstAider",Http] params:parmDic];
}
// 急救员 - 搜索员工
- (void)getSearchEmployee:(NSMutableDictionary *)parmDic {
[httpRequest httpGetRequest:[NSString stringWithFormat:@"%@/health-emergency/api/emergency/order/searchEmployee",Http] params:parmDic];
}
// 急救员 - 邀请进群
- (void)postAddGroupUser:(NSMutableDictionary *)parmDic {
[httpRequest httpPostRequest:[NSString stringWithFormat:@"%@/health-emergency/api/emergency/addGroupUser",Http] params:parmDic];
}
// 急救员 - 移除群成员
- (void)postRemoveGroupUser:(NSMutableDictionary *)parmDic {
[httpRequest httpPostRequest:[NSString stringWithFormat:@"%@/health-emergency/api/emergency/removeGroupUser",Http] params:parmDic];
}
// 急救员 - 群成员列表
- (void)getGroupMemberList:(NSMutableDictionary *)parmDic {
[httpRequest httpGetRequest:[NSString stringWithFormat:@"%@/health-emergency/api/emergency/order/getGroupMemberList",Http] params:parmDic];
}
// 急救员 - 实际群成员列表(经腾讯IM验证,含isSelf字段)
- (void)getActualGroupMemberList:(NSMutableDictionary *)parmDic {
[httpRequest httpGetRequest:[NSString stringWithFormat:@"%@/health-emergency/api/emergency/order/getActualGroupMemberList",Http] params:parmDic];
}
// 急救员 - 宣教资源详情
- (void)getFirstAidResourceDetail:(NSMutableDictionary *)parmDic {
[httpRequest httpGetRequest:[NSString stringWithFormat:@"%@/health-emergency/api/emergency/firstAid/resource/resource/queryById",Http] params:parmDic];
}
- (void)getNearbyHomePageHospitalPoint:(NSMutableDictionary *)parmDic {
[httpRequest httpGetRequest:[NSString stringWithFormat:@"%@/medical-center/api/resource/nearbyResource",Http] params:parmDic];
@@ -913,6 +953,7 @@
[httpRequest httpGetRequest:[NSString stringWithFormat:@"%@/health-watch/api/watchData/userWatchDataAll",Http] params:parmDic];
}
- (void)getBindWatchDevice:(NSMutableDictionary *)parmDic {
[httpRequest httpGetRequest:[NSString stringWithFormat:@"%@/health-watch/watch/watchDevice/app/bindDevice",Http] params:parmDic];
@@ -922,4 +963,10 @@
[httpRequest httpGetRequest:[NSString stringWithFormat:@"%@/health-watch/watch/watchDevice/app/unbundleDevice",Http] params:parmDic];
}
//外部接口
- (void)postPointsUnmberData:(NSMutableDictionary *)parmDic {
[httpRequest httpOtherPostRequest:[NSString stringWithFormat:@"%@/api/v1/users/point-details",OtherHttp] params:parmDic];
}
@end
@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>AvailableLibraries</key>
<array>
<dict>
<key>BinaryPath</key>
<string>WechatOpenSDK.framework/WechatOpenSDK</string>
<key>LibraryIdentifier</key>
<string>ios-arm64</string>
<key>LibraryPath</key>
<string>WechatOpenSDK.framework</string>
<key>SupportedArchitectures</key>
<array>
<string>arm64</string>
</array>
<key>SupportedPlatform</key>
<string>ios</string>
</dict>
<dict>
<key>BinaryPath</key>
<string>WechatOpenSDK.framework/WechatOpenSDK</string>
<key>LibraryIdentifier</key>
<string>ios-arm64_x86_64-simulator</string>
<key>LibraryPath</key>
<string>WechatOpenSDK.framework</string>
<key>SupportedArchitectures</key>
<array>
<string>arm64</string>
<string>x86_64</string>
</array>
<key>SupportedPlatform</key>
<string>ios</string>
<key>SupportedPlatformVariant</key>
<string>simulator</string>
</dict>
</array>
<key>CFBundlePackageType</key>
<string>XFWK</string>
<key>XCFrameworkFormatVersion</key>
<string>1.0</string>
</dict>
</plist>
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>CA92.1</string>
</array>
</dict>
</array>
</dict>
</plist>
@@ -0,0 +1,167 @@
重要!
SDK2.0.5
1. 支持模块化集成​:XCFramework 头文件引用改为标准化格式 #import <WechatOpenSDK/WXApi.h>,解决路径冲突并支持 Swift/ObjC 混合开发
2. 修复openWXApp偶现失败的问题
SDK2.0.4
1.增加privacy manifest文件
2.修复跳微信时可能卡顿的问题
SDK2.0.2
1. 优化XCFramework打包方式
SDK2.0.1
1. SDK支持XCFramework
SDK2.0.0
1. 分享能力支持内容防篡改校验
SDK1.9.9
1. 授权登录支持关闭自动授权
2. 分享支持添加签名,防止篡改
SDK1.9.7
1. 适配CocoaPods
SDK1.9.6
1. 适配iOS 16,减少读写剪切板
SDK1.9.4
1. 修复授权登录取消/拒绝时state字段没有带回
SDK1.9.3
1. 新增发起二维码支付能力
SDK1.9.2
1. 新增发起企微客服会话能力
SDK1.9.1
1. 音乐视频分享类型增加运营H5字段
SDK1.8.9
1. 增加音乐视频分享类型
SDK1.8.8
1. 增加游戏直播消息类型
SDK1.8.7.1
1. 修复Xcode11以下编译不通过
SDK1.8.7
1. 修复iPadOS,未安装微信的情况下,因为UA问题无法授权登录
2. 修复未安装微信的情况下, 适配了UIScene的App因为UIAlertView Crash
3. 增加Universal Link检测函数
SDK1.8.6.2
1. 修改包含"UIWebView"字符的类名
SDK1.8.6.1
1.短信授权登录使用的UIWebview切换成WKWebview
SDK1.8.6
1. 支持Universal Link拉起微信以及返回App
2. SDK移除MTA库
SDK1.8.5
1. 更换MTA库:取消对剪切板的访问, 防止和其他SDK竞争导致crash
2. NSMutableArray的MTA分类方法改名,减少命名冲突
3. 不含支付功能版本移除非税支付和医保支付接口
4. 分享音乐支持填写歌词和高清封面图
SDK1.8.4
1. 调整分享图片大小限制
2. 新增openBusinessView接口
SDK1.8.3
1. SDK增加调起微信刷卡支付接口
2. SDK增加小程序订阅消息接口
3. 修复小程序订阅消息接口没有resp的问题
SDK1.8.2
1. SDK增加开发票授权 WXInvoiceAuthInsert
2. SDK增加非税接口 WXNontaxPay
3. SDK增加医保接口 WXPayInsurance
4. 更换MTA库
SDK1.8.1
1. SDK打开小程序支持指定版本(体验,开发,正式版)
2. SDK分享小程序支持指定版本(体验,开发,正式版)
3. SDK支持输出log日志
SDK1.8.0
1. SDK支持打开小程序
2. SDK分享小程序支持shareTicket
SDK1.7.9
1. SDK订阅一次性消息
SDK1.7.8
1 SDK分享小程序支持大图
SDK1.7.7
1 增加SDK分享小程序
2 增加选择发票接口
SDK1.7.6
1. 提高稳定性
1 修复mta崩溃
2 新增接口支持开发者关闭mta数据统计上报
SDK1.7.5
1. 提高稳定性
2. 加快registerApp接口启动速度
SDK1.7.4
1. 更新支持iOS启用 ATS(App Transport Security)
2. 需要在工程中链接CFNetwork.framework
3. 在工程配置中的”Other Linker Flags”中加入”-Objc -all_load”
SDK1.7.3
1. 增强稳定性,适配iOS10
2. 修复小于32K的jpg格式缩略图设置失败的问题
SDK1.7.2
1. 修复因CTTeleponyNetworkInfo引起的崩溃问题
SDK1.7.1
1. 支持兼容ipv6(提升稳定性)
2. xCode Version 7.3.1 (7D1014) 编译
SDK1.7
1. 支持兼容ipv6
2. 修复若干问题增强稳定性
SDK1.6.3
1. xCode7.2 构建的sdk包。
2. 请使用xCode7.2进行编译。
3. 需要在Build Phases中Link Security.framework
4. 修复若干小问题。
SDK1.6.2
1、xCode7.1 构建的sdk包
2、请使用xCode7.1进行编译
SDK1.6.1
1、修复armv7s下,bitcode可能编译不过
2、解决warning
SDK1.6
1、iOS 9系统策略更新,限制了http协议的访问,此外应用需要在“Info.plist”中将要使用的URL Schemes列为白名单,才可正常检查其他应用是否安装。
受此影响,当你的应用在iOS 9中需要使用微信SDK的相关能力(分享、收藏、支付、登录等)时,需要在“Info.plist”里增加如下代码:
<key>LSApplicationQueriesSchemes</key>
<array>
<string>weixin</string>
</array>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
2、开发者需要在工程中链接上 CoreTelephony.framework
3、解决bitcode编译不过问题
SDK1.5
1、废弃safeSendReq:接口,使用sendReq:即可。
2、新增+(BOOL) sendAuthReq:(SendAuthReq*) req viewController : (UIViewController*) viewController delegate:(id<WXApiDelegate>) delegate;
支持未安装微信情况下Auth,具体见WXApi.h接口描述
3、微信开放平台新增了微信模块用户统计功能,便于开发者统计微信功能模块的用户使用和活跃情况。开发者需要在工程中链接上:SystemConfiguration.framework,libz.dylib,libsqlite3.0.dylib。
@@ -0,0 +1,231 @@
//
// WXApi.h
// 所有Api接口
//
// Created by Wechat on 12-2-28.
// Copyright (c) 2012年 Tencent. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "WXApiObject.h"
NS_ASSUME_NONNULL_BEGIN
typedef BOOL(^WXGrantReadPasteBoardPermissionCompletion)(void);
#pragma mark - WXApiDelegate
/*! @brief 接收并处理来自微信终端程序的事件消息
*
* 接收并处理来自微信终端程序的事件消息,期间微信界面会切换到第三方应用程序。
* WXApiDelegate 会在handleOpenURL:delegate:中使用并触发。
*/
@protocol WXApiDelegate <NSObject>
@optional
/*! @brief 收到一个来自微信的请求,第三方应用程序处理完后调用sendResp向微信发送结果
*
* 收到一个来自微信的请求,异步处理完成后必须调用sendResp发送处理结果给微信。
* 可能收到的请求有GetMessageFromWXReq、ShowMessageFromWXReq等。
* @param req 具体请求内容,是自动释放的
*/
- (void)onReq:(BaseReq*)req;
/*! @brief 发送一个sendReq后,收到微信的回应
*
* 收到一个来自微信的处理结果。调用一次sendReq后会收到onResp。
* 可能收到的处理结果有SendMessageToWXResp、SendAuthResp等。
* @param resp具体的回应内容,是自动释放的
*/
- (void)onResp:(BaseResp*)resp;
/* ! @brief 用于在iOS16以及以上系统上,控制OpenSDK是否读取剪切板中微信传递的数据以及读取的时机
* 在iOS16以及以上系统,在SDK需要读取剪切板中微信写入的数据时,会回调该方法。没有实现默认会直接读取微信通过剪切板传递过来的数据
* 注意:
* 1. 只在iOS16以及以上的系统版本上回调;
* 2. 不实现时,OpenSDK会直接调用读取剪切板接口,读取微信传递过来的数据;
* 3. 若实现该方法:开发者需要通过调用completion(), 支持异步,通知SDK允许读取剪切板中微信传递的数据,
* 不调用completion()则代表不授权OpenSDK读取剪切板,会导致收不到onReq:, onResp:回调,无法后续业务流程。请谨慎使用
* 4. 不要长时间持有completion不释放,可能会导致内存泄漏。
*/
- (void)onNeedGrantReadPasteBoardPermissionWithURL:(nonnull NSURL *)openURL completion:(nonnull WXGrantReadPasteBoardPermissionCompletion)completion;
@end
#pragma mark - WXApiLogDelegate
@protocol WXApiLogDelegate <NSObject>
- (void)onLog:(NSString*)log logLevel:(WXLogLevel)level;
@end
#pragma mark - WXApi
/*! @brief 微信Api接口函数类
*
* 该类封装了微信终端SDK的所有接口
*/
@interface WXApi : NSObject
/*! @brief WXApi的成员函数,向微信终端程序注册第三方应用。
*
* 需要在每次启动第三方应用程序时调用。
* @attention 请保证在主线程中调用此函数
* @param appid 微信开发者ID
* @param universalLink 微信开发者Universal Link
* @return 成功返回YES,失败返回NO。
*/
+ (BOOL)registerApp:(NSString *)appid universalLink:(NSString *)universalLink;
/*! @brief 处理旧版微信通过URL启动App时传递的数据
*
* 需要在 application:openURL:sourceApplication:annotation:或者application:handleOpenURL中调用。
* @param url 微信启动第三方应用时传递过来的URL
* @param delegate WXApiDelegate对象,用来接收微信触发的消息。
* @return 成功返回YES,失败返回NO。
*/
+ (BOOL)handleOpenURL:(NSURL *)url delegate:(nullable id<WXApiDelegate>)delegate;
/*! @brief 处理微信通过Universal Link启动App时传递的数据
*
* 需要在 application:continueUserActivity:restorationHandler:中调用。
* @param userActivity 微信启动第三方应用时系统API传递过来的userActivity
* @param delegate WXApiDelegate对象,用来接收微信触发的消息。
* @return 成功返回YES,失败返回NO。
*/
+ (BOOL)handleOpenUniversalLink:(NSUserActivity *)userActivity delegate:(nullable id<WXApiDelegate>)delegate;
/*! @brief 检查微信是否已被用户安装
*
* @return 微信已安装返回YES,未安装返回NO。
*/
+ (BOOL)isWXAppInstalled;
/*! @brief 判断当前微信的版本是否支持OpenApi
*
* @return 支持返回YES,不支持返回NO。
*/
+ (BOOL)isWXAppSupportApi;
/*! @brief 判断当前微信的版本是否支持分享微信状态功能
*
* @attention 需在工程LSApplicationQueriesSchemes配置中添加weixinStateAPI
* @return 支持返回YES,不支持返回NO。
*/
+ (BOOL)isWXAppSupportStateAPI;
#ifndef BUILD_WITHOUT_PAY
/*! @brief 判断当前微信的版本是否支持二维码拉起微信支付
*
* @attention 需在工程LSApplicationQueriesSchemes配置中添加weixinQRCodePayAPI
* @return 支持返回YES,不支持返回NO。
*/
+ (BOOL)isWXAppSupportQRCodePayAPI;
#endif
/*! @brief 获取微信的itunes安装地址
*
* @return 微信的安装地址字符串。
*/
+ (NSString *)getWXAppInstallUrl;
/*! @brief 获取当前微信SDK的版本号
*
* @return 返回当前微信SDK的版本号
*/
+ (NSString *)getApiVersion;
/*! @brief 打开微信
*
* @return 成功返回YES,失败返回NO。
*/
+ (BOOL)openWXApp;
/*! @brief 发送请求到微信,等待微信返回onResp
*
* 函数调用后,会切换到微信的界面。第三方应用程序等待微信返回onResp。微信在异步处理完成后一定会调用onResp。支持以下类型
* SendAuthReq、SendMessageToWXReq、PayReq等。
* @param req 具体的发送请求。
* @param completion 调用结果回调block
*/
+ (void)sendReq:(BaseReq *)req completion:(void (^ __nullable)(BOOL success))completion;
/*! @brief 收到微信onReq的请求,发送对应的应答给微信,并切换到微信界面
*
* 函数调用后,会切换到微信的界面。第三方应用程序收到微信onReq的请求,异步处理该请求,完成后必须调用该函数。可能发送的相应有
* GetMessageFromWXResp、ShowMessageFromWXResp等。
* @param resp 具体的应答内容
* @param completion 调用结果回调block
*/
+ (void)sendResp:(BaseResp*)resp completion:(void (^ __nullable)(BOOL success))completion;
/*! @brief 发送Auth请求到微信,支持用户没安装微信,等待微信返回onResp
*
* 函数调用后,会切换到微信的界面。第三方应用程序等待微信返回onResp。微信在异步处理完成后一定会调用onResp。支持SendAuthReq类型。
* @param req 具体的发送请求。
* @param viewController 当前界面对象。
* @param delegate WXApiDelegate对象,用来接收微信触发的消息。
* @param completion 调用结果回调block
*/
+ (void)sendAuthReq:(SendAuthReq *)req viewController:(UIViewController*)viewController delegate:(nullable id<WXApiDelegate>)delegate completion:(void (^ __nullable)(BOOL success))completion;
/*! @brief 测试函数,用于排查当前App通过Universal Link方式分享到微信的流程
注意1: 调用自检函数之前必须要先调用registerApp:universalLink接口, 并确认调用成功
注意2: 自检过程中会有Log产生,可以先调用startLogByLevel函数,根据Log排查问题
注意3: 会多次回调block
注意4: 仅用于新接入SDK时调试使用,请勿在正式环境的调用
*
* 当completion回调的step为WXULCheckStepFinal时,表示检测通过,Universal Link接入成功
* @param completion 回调Block
*/
+ (void)checkUniversalLinkReady:(nonnull WXCheckULCompletion)completion;
/*! @brief WXApi的成员函数,接受微信的log信息。byBlock
注意1:SDK会强引用这个block,注意不要导致内存泄漏,注意不要导致内存泄漏
注意2:调用过一次startLog by block之后,如果再调用一次任意方式的startLoad,会释放上一次logBlock,不再回调上一个logBlock
*
* @param level 打印log的级别
* @param logBlock 打印log的回调block
*/
+ (void)startLogByLevel:(WXLogLevel)level logBlock:(WXLogBolock)logBlock;
/*! @brief WXApi的成员函数,接受微信的log信息。byDelegate
注意1:sdk会弱引用这个delegate,这里可加任意对象为代理,不需要与WXApiDelegate同一个对象
注意2:调用过一次startLog by delegate之后,再调用一次任意方式的startLoad,不会再回调上一个logDelegate对象
* @param level 打印log的级别
* @param logDelegate 打印log的回调代理,
*/
+ (void)startLogByLevel:(WXLogLevel)level logDelegate:(id<WXApiLogDelegate>)logDelegate;
/*! @brief 停止打印log,会清理block或者delegate为空,释放block
* @param
*/
+ (void)stopLog;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,68 @@
//
// WechatAuthSDK.h
// WechatAuthSDK
//
// Created by 李凯 on 13-11-29.
// Copyright (c) 2013年 Tencent. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
enum AuthErrCode {
WechatAuth_Err_Ok = 0, //Auth成功
WechatAuth_Err_NormalErr = -1, //普通错误
WechatAuth_Err_NetworkErr = -2, //网络错误
WechatAuth_Err_GetQrcodeFailed = -3, //获取二维码失败
WechatAuth_Err_Cancel = -4, //用户取消授权
WechatAuth_Err_Timeout = -5, //超时
};
@protocol WechatAuthAPIDelegate<NSObject>
@optional
- (void)onAuthGotQrcode:(UIImage *)image; //得到二维码
- (void)onQrcodeScanned; //二维码被扫描
- (void)onAuthFinish:(int)errCode AuthCode:(nullable NSString *)authCode; //成功登录
@end
@interface WechatAuthSDK : NSObject{
NSString *_sdkVersion;
__weak id<WechatAuthAPIDelegate> _delegate;
}
@property(nonatomic, weak, nullable) id<WechatAuthAPIDelegate> delegate;
@property(nonatomic, readonly) NSString *sdkVersion; //authSDK版本号
/*! @brief 发送登录请求,等待WechatAuthAPIDelegate回调
*
* @param appId 微信开发者ID
* @param nonceStr 一个随机的尽量不重复的字符串,用来使得每次的signature不同
* @param timeStamp 时间戳
* @param scope 应用授权作用域,拥有多个作用域用逗号(,)分隔
* @param signature 签名
* @param schemeData 会在扫码后拼在scheme后
* @return 成功返回YES,失败返回NO
注:该实现只保证同时只有一个Auth在运行,Auth未完成或未Stop再次调用Auth接口时会返回NO。
*/
- (BOOL)Auth:(NSString *)appId
nonceStr:(NSString *)nonceStr
timeStamp:(NSString *)timeStamp
scope:(NSString *)scope
signature:(NSString *)signature
schemeData:(nullable NSString *)schemeData;
/*! @brief 暂停登录请求
*
* @return 成功返回YES,失败返回NO。
*/
- (BOOL)StopAuth;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,9 @@
// WechatOpenSDK.h
//
// Created by Wechat.
// Copyright (c) 2012年 Tencent. All rights reserved.
//
#import <WechatOpenSDK/WXApi.h>
#import <WechatOpenSDK/WXApiObject.h>
#import <WechatOpenSDK/WechatAuthSDK.h>
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<plist version="1.0">
<dict>
<key>CFBundleIdentifier</key>
<string>com.tencent.WechatOpenSDK</string>
<key>CFBundleVersion</key>
<string>1.0.0</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>12.0</string>
<key>CFBundleExecutable</key>
<string>WechatOpenSDK</string>
</dict>
</plist>
@@ -0,0 +1,5 @@
framework module WechatOpenSDK {
umbrella header "WechatOpenSDK.h"
export *
link "WechatOpenSDK"
}
@@ -0,0 +1,231 @@
//
// WXApi.h
// 所有Api接口
//
// Created by Wechat on 12-2-28.
// Copyright (c) 2012年 Tencent. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "WXApiObject.h"
NS_ASSUME_NONNULL_BEGIN
typedef BOOL(^WXGrantReadPasteBoardPermissionCompletion)(void);
#pragma mark - WXApiDelegate
/*! @brief 接收并处理来自微信终端程序的事件消息
*
* 接收并处理来自微信终端程序的事件消息,期间微信界面会切换到第三方应用程序。
* WXApiDelegate 会在handleOpenURL:delegate:中使用并触发。
*/
@protocol WXApiDelegate <NSObject>
@optional
/*! @brief 收到一个来自微信的请求,第三方应用程序处理完后调用sendResp向微信发送结果
*
* 收到一个来自微信的请求,异步处理完成后必须调用sendResp发送处理结果给微信。
* 可能收到的请求有GetMessageFromWXReq、ShowMessageFromWXReq等。
* @param req 具体请求内容,是自动释放的
*/
- (void)onReq:(BaseReq*)req;
/*! @brief 发送一个sendReq后,收到微信的回应
*
* 收到一个来自微信的处理结果。调用一次sendReq后会收到onResp。
* 可能收到的处理结果有SendMessageToWXResp、SendAuthResp等。
* @param resp具体的回应内容,是自动释放的
*/
- (void)onResp:(BaseResp*)resp;
/* ! @brief 用于在iOS16以及以上系统上,控制OpenSDK是否读取剪切板中微信传递的数据以及读取的时机
* 在iOS16以及以上系统,在SDK需要读取剪切板中微信写入的数据时,会回调该方法。没有实现默认会直接读取微信通过剪切板传递过来的数据
* 注意:
* 1. 只在iOS16以及以上的系统版本上回调;
* 2. 不实现时,OpenSDK会直接调用读取剪切板接口,读取微信传递过来的数据;
* 3. 若实现该方法:开发者需要通过调用completion(), 支持异步,通知SDK允许读取剪切板中微信传递的数据,
* 不调用completion()则代表不授权OpenSDK读取剪切板,会导致收不到onReq:, onResp:回调,无法后续业务流程。请谨慎使用
* 4. 不要长时间持有completion不释放,可能会导致内存泄漏。
*/
- (void)onNeedGrantReadPasteBoardPermissionWithURL:(nonnull NSURL *)openURL completion:(nonnull WXGrantReadPasteBoardPermissionCompletion)completion;
@end
#pragma mark - WXApiLogDelegate
@protocol WXApiLogDelegate <NSObject>
- (void)onLog:(NSString*)log logLevel:(WXLogLevel)level;
@end
#pragma mark - WXApi
/*! @brief 微信Api接口函数类
*
* 该类封装了微信终端SDK的所有接口
*/
@interface WXApi : NSObject
/*! @brief WXApi的成员函数,向微信终端程序注册第三方应用。
*
* 需要在每次启动第三方应用程序时调用。
* @attention 请保证在主线程中调用此函数
* @param appid 微信开发者ID
* @param universalLink 微信开发者Universal Link
* @return 成功返回YES,失败返回NO。
*/
+ (BOOL)registerApp:(NSString *)appid universalLink:(NSString *)universalLink;
/*! @brief 处理旧版微信通过URL启动App时传递的数据
*
* 需要在 application:openURL:sourceApplication:annotation:或者application:handleOpenURL中调用。
* @param url 微信启动第三方应用时传递过来的URL
* @param delegate WXApiDelegate对象,用来接收微信触发的消息。
* @return 成功返回YES,失败返回NO。
*/
+ (BOOL)handleOpenURL:(NSURL *)url delegate:(nullable id<WXApiDelegate>)delegate;
/*! @brief 处理微信通过Universal Link启动App时传递的数据
*
* 需要在 application:continueUserActivity:restorationHandler:中调用。
* @param userActivity 微信启动第三方应用时系统API传递过来的userActivity
* @param delegate WXApiDelegate对象,用来接收微信触发的消息。
* @return 成功返回YES,失败返回NO。
*/
+ (BOOL)handleOpenUniversalLink:(NSUserActivity *)userActivity delegate:(nullable id<WXApiDelegate>)delegate;
/*! @brief 检查微信是否已被用户安装
*
* @return 微信已安装返回YES,未安装返回NO。
*/
+ (BOOL)isWXAppInstalled;
/*! @brief 判断当前微信的版本是否支持OpenApi
*
* @return 支持返回YES,不支持返回NO。
*/
+ (BOOL)isWXAppSupportApi;
/*! @brief 判断当前微信的版本是否支持分享微信状态功能
*
* @attention 需在工程LSApplicationQueriesSchemes配置中添加weixinStateAPI
* @return 支持返回YES,不支持返回NO。
*/
+ (BOOL)isWXAppSupportStateAPI;
#ifndef BUILD_WITHOUT_PAY
/*! @brief 判断当前微信的版本是否支持二维码拉起微信支付
*
* @attention 需在工程LSApplicationQueriesSchemes配置中添加weixinQRCodePayAPI
* @return 支持返回YES,不支持返回NO。
*/
+ (BOOL)isWXAppSupportQRCodePayAPI;
#endif
/*! @brief 获取微信的itunes安装地址
*
* @return 微信的安装地址字符串。
*/
+ (NSString *)getWXAppInstallUrl;
/*! @brief 获取当前微信SDK的版本号
*
* @return 返回当前微信SDK的版本号
*/
+ (NSString *)getApiVersion;
/*! @brief 打开微信
*
* @return 成功返回YES,失败返回NO。
*/
+ (BOOL)openWXApp;
/*! @brief 发送请求到微信,等待微信返回onResp
*
* 函数调用后,会切换到微信的界面。第三方应用程序等待微信返回onResp。微信在异步处理完成后一定会调用onResp。支持以下类型
* SendAuthReq、SendMessageToWXReq、PayReq等。
* @param req 具体的发送请求。
* @param completion 调用结果回调block
*/
+ (void)sendReq:(BaseReq *)req completion:(void (^ __nullable)(BOOL success))completion;
/*! @brief 收到微信onReq的请求,发送对应的应答给微信,并切换到微信界面
*
* 函数调用后,会切换到微信的界面。第三方应用程序收到微信onReq的请求,异步处理该请求,完成后必须调用该函数。可能发送的相应有
* GetMessageFromWXResp、ShowMessageFromWXResp等。
* @param resp 具体的应答内容
* @param completion 调用结果回调block
*/
+ (void)sendResp:(BaseResp*)resp completion:(void (^ __nullable)(BOOL success))completion;
/*! @brief 发送Auth请求到微信,支持用户没安装微信,等待微信返回onResp
*
* 函数调用后,会切换到微信的界面。第三方应用程序等待微信返回onResp。微信在异步处理完成后一定会调用onResp。支持SendAuthReq类型。
* @param req 具体的发送请求。
* @param viewController 当前界面对象。
* @param delegate WXApiDelegate对象,用来接收微信触发的消息。
* @param completion 调用结果回调block
*/
+ (void)sendAuthReq:(SendAuthReq *)req viewController:(UIViewController*)viewController delegate:(nullable id<WXApiDelegate>)delegate completion:(void (^ __nullable)(BOOL success))completion;
/*! @brief 测试函数,用于排查当前App通过Universal Link方式分享到微信的流程
注意1: 调用自检函数之前必须要先调用registerApp:universalLink接口, 并确认调用成功
注意2: 自检过程中会有Log产生,可以先调用startLogByLevel函数,根据Log排查问题
注意3: 会多次回调block
注意4: 仅用于新接入SDK时调试使用,请勿在正式环境的调用
*
* 当completion回调的step为WXULCheckStepFinal时,表示检测通过,Universal Link接入成功
* @param completion 回调Block
*/
+ (void)checkUniversalLinkReady:(nonnull WXCheckULCompletion)completion;
/*! @brief WXApi的成员函数,接受微信的log信息。byBlock
注意1:SDK会强引用这个block,注意不要导致内存泄漏,注意不要导致内存泄漏
注意2:调用过一次startLog by block之后,如果再调用一次任意方式的startLoad,会释放上一次logBlock,不再回调上一个logBlock
*
* @param level 打印log的级别
* @param logBlock 打印log的回调block
*/
+ (void)startLogByLevel:(WXLogLevel)level logBlock:(WXLogBolock)logBlock;
/*! @brief WXApi的成员函数,接受微信的log信息。byDelegate
注意1:sdk会弱引用这个delegate,这里可加任意对象为代理,不需要与WXApiDelegate同一个对象
注意2:调用过一次startLog by delegate之后,再调用一次任意方式的startLoad,不会再回调上一个logDelegate对象
* @param level 打印log的级别
* @param logDelegate 打印log的回调代理,
*/
+ (void)startLogByLevel:(WXLogLevel)level logDelegate:(id<WXApiLogDelegate>)logDelegate;
/*! @brief 停止打印log,会清理block或者delegate为空,释放block
* @param
*/
+ (void)stopLog;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,68 @@
//
// WechatAuthSDK.h
// WechatAuthSDK
//
// Created by 李凯 on 13-11-29.
// Copyright (c) 2013年 Tencent. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
enum AuthErrCode {
WechatAuth_Err_Ok = 0, //Auth成功
WechatAuth_Err_NormalErr = -1, //普通错误
WechatAuth_Err_NetworkErr = -2, //网络错误
WechatAuth_Err_GetQrcodeFailed = -3, //获取二维码失败
WechatAuth_Err_Cancel = -4, //用户取消授权
WechatAuth_Err_Timeout = -5, //超时
};
@protocol WechatAuthAPIDelegate<NSObject>
@optional
- (void)onAuthGotQrcode:(UIImage *)image; //得到二维码
- (void)onQrcodeScanned; //二维码被扫描
- (void)onAuthFinish:(int)errCode AuthCode:(nullable NSString *)authCode; //成功登录
@end
@interface WechatAuthSDK : NSObject{
NSString *_sdkVersion;
__weak id<WechatAuthAPIDelegate> _delegate;
}
@property(nonatomic, weak, nullable) id<WechatAuthAPIDelegate> delegate;
@property(nonatomic, readonly) NSString *sdkVersion; //authSDK版本号
/*! @brief 发送登录请求,等待WechatAuthAPIDelegate回调
*
* @param appId 微信开发者ID
* @param nonceStr 一个随机的尽量不重复的字符串,用来使得每次的signature不同
* @param timeStamp 时间戳
* @param scope 应用授权作用域,拥有多个作用域用逗号(,)分隔
* @param signature 签名
* @param schemeData 会在扫码后拼在scheme后
* @return 成功返回YES,失败返回NO
注:该实现只保证同时只有一个Auth在运行,Auth未完成或未Stop再次调用Auth接口时会返回NO。
*/
- (BOOL)Auth:(NSString *)appId
nonceStr:(NSString *)nonceStr
timeStamp:(NSString *)timeStamp
scope:(NSString *)scope
signature:(NSString *)signature
schemeData:(nullable NSString *)schemeData;
/*! @brief 暂停登录请求
*
* @return 成功返回YES,失败返回NO。
*/
- (BOOL)StopAuth;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,9 @@
// WechatOpenSDK.h
//
// Created by Wechat.
// Copyright (c) 2012年 Tencent. All rights reserved.
//
#import <WechatOpenSDK/WXApi.h>
#import <WechatOpenSDK/WXApiObject.h>
#import <WechatOpenSDK/WechatAuthSDK.h>
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<plist version="1.0">
<dict>
<key>CFBundleIdentifier</key>
<string>com.tencent.WechatOpenSDK</string>
<key>CFBundleVersion</key>
<string>1.0.0</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>12.0</string>
<key>CFBundleExecutable</key>
<string>WechatOpenSDK</string>
</dict>
</plist>
@@ -0,0 +1,5 @@
framework module WechatOpenSDK {
umbrella header "WechatOpenSDK.h"
export *
link "WechatOpenSDK"
}
@@ -0,0 +1,81 @@
//
// XJWeChatManager.h
// XinjiangProject
//
// 微信分享管理类(手动集成 WechatOpenSDK-NoPay
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
/// 微信分享结果回调
typedef void(^XJWeChatShareCompletion)(BOOL success, NSString *_Nullable errorMsg);
@interface XJWeChatManager : NSObject
+ (instancetype)sharedManager;
#pragma mark - 注册
/// 注册微信 SDK(需在 AppDelegate didFinishLaunching 中调用)
/// @param appID 微信开放平台 AppID
/// @param universalLink 通用链接
- (BOOL)registerWithAppID:(NSString *)appID universalLink:(NSString *)universalLink;
/// 处理微信 URL 回调(AppDelegate handleOpenURL 中调用)
- (BOOL)handleOpenURL:(NSURL *)url;
/// 处理微信 Universal Link 回调(AppDelegate continueUserActivity 中调用)
- (BOOL)handleOpenUniversalLink:(NSUserActivity *)userActivity;
#pragma mark - 分享
/// 分享文字
- (void)shareText:(NSString *)text
toScene:(int)scene
completion:(XJWeChatShareCompletion _Nullable)completion;
/// 分享图片(NSData
- (void)shareImageData:(NSData *)imageData
toScene:(int)scene
completion:(XJWeChatShareCompletion _Nullable)completion;
/// 分享网页
/// @param title 标题
/// @param desc 描述
/// @param webpageUrl 网页链接
/// @param thumbImage 缩略图(不超过 32KB
/// @param scene 分享目标:WXSceneSession=好友, WXSceneTimeline=朋友圈
- (void)shareWebpageWithTitle:(NSString *)title
desc:(NSString *)desc
webpageUrl:(NSString *)webpageUrl
thumbImage:(UIImage *_Nullable)thumbImage
toScene:(int)scene
completion:(XJWeChatShareCompletion _Nullable)completion;
/// 分享小程序卡片
/// @param title 标题
/// @param desc 描述
/// @param webpageUrl 兼容低版本网页链接
/// @param userName 小程序原始IDgh_ 开头)
/// @param path 小程序页面路径
/// @param miniProgramType 小程序类型:0=正式版, 1=开发版, 2=体验版
- (void)shareMiniProgramWithTitle:(NSString *)title
desc:(NSString *)desc
webpageUrl:(NSString *)webpageUrl
userName:(NSString *)userName
path:(NSString *)path
miniProgramType:(NSUInteger)miniProgramType
thumbImage:(UIImage *_Nullable)thumbImage
completion:(XJWeChatShareCompletion _Nullable)completion;
#pragma mark - 工具
/// 是否安装微信
- (BOOL)isWeChatInstalled;
/// 微信是否支持 OpenAPI
- (BOOL)isWeChatSupportApi;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,160 @@
//
// XJWeChatManager.m
// XinjiangProject
//
// 微信分享管理类
//
#import "XJWeChatManager.h"
#import <WechatOpenSDK/WXApi.h>
@interface XJWeChatManager () <WXApiDelegate>
@property (nonatomic, copy) XJWeChatShareCompletion shareCompletion;
@end
@implementation XJWeChatManager
#pragma mark - ————— 单例 —————
+ (instancetype)sharedManager {
static XJWeChatManager *instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[XJWeChatManager alloc] init];
});
return instance;
}
#pragma mark - ————— 注册 —————
- (BOOL)registerWithAppID:(NSString *)appID universalLink:(NSString *)universalLink {
return [WXApi registerApp:appID universalLink:universalLink];
}
- (BOOL)handleOpenURL:(NSURL *)url {
return [WXApi handleOpenURL:url delegate:self];
}
- (BOOL)handleOpenUniversalLink:(NSUserActivity *)userActivity {
return [WXApi handleOpenUniversalLink:userActivity delegate:self];
}
#pragma mark - ————— 分享 —————
- (void)shareText:(NSString *)text
toScene:(int)scene
completion:(XJWeChatShareCompletion)completion {
self.shareCompletion = completion;
SendMessageToWXReq *req = [[SendMessageToWXReq alloc] init];
req.bText = YES;
req.text = text;
req.scene = scene;
[WXApi sendReq:req completion:nil];
}
- (void)shareImageData:(NSData *)imageData
toScene:(int)scene
completion:(XJWeChatShareCompletion)completion {
self.shareCompletion = completion;
WXMediaMessage *message = [WXMediaMessage message];
message.thumbData = nil;
WXImageObject *imageObject = [WXImageObject object];
imageObject.imageData = imageData;
message.mediaObject = imageObject;
SendMessageToWXReq *req = [[SendMessageToWXReq alloc] init];
req.bText = NO;
req.message = message;
req.scene = scene;
[WXApi sendReq:req completion:nil];
}
- (void)shareWebpageWithTitle:(NSString *)title
desc:(NSString *)desc
webpageUrl:(NSString *)webpageUrl
thumbImage:(UIImage *)thumbImage
toScene:(int)scene
completion:(XJWeChatShareCompletion)completion {
self.shareCompletion = completion;
WXMediaMessage *message = [WXMediaMessage message];
message.title = title;
message.description = desc;
if (thumbImage) {
message.thumbData = UIImageJPEGRepresentation(thumbImage, 0.5);
}
WXWebpageObject *webpageObject = [WXWebpageObject object];
webpageObject.webpageUrl = webpageUrl;
message.mediaObject = webpageObject;
SendMessageToWXReq *req = [[SendMessageToWXReq alloc] init];
req.bText = NO;
req.message = message;
req.scene = scene;
[WXApi sendReq:req completion:nil];
}
- (void)shareMiniProgramWithTitle:(NSString *)title
desc:(NSString *)desc
webpageUrl:(NSString *)webpageUrl
userName:(NSString *)userName
path:(NSString *)path
miniProgramType:(NSUInteger)miniProgramType
thumbImage:(UIImage *)thumbImage
completion:(XJWeChatShareCompletion)completion {
self.shareCompletion = completion;
WXMediaMessage *message = [WXMediaMessage message];
message.title = title;
message.description = desc;
if (thumbImage) {
message.thumbData = UIImageJPEGRepresentation(thumbImage, 0.5);
}
WXMiniProgramObject *miniProgramObject = [WXMiniProgramObject object];
miniProgramObject.webpageUrl = webpageUrl;
miniProgramObject.userName = userName;
miniProgramObject.path = path;
miniProgramObject.miniProgramType = miniProgramType;
message.mediaObject = miniProgramObject;
SendMessageToWXReq *req = [[SendMessageToWXReq alloc] init];
req.bText = NO;
req.message = message;
req.scene = WXSceneSession;
[WXApi sendReq:req completion:nil];
}
#pragma mark - ————— 工具 —————
- (BOOL)isWeChatInstalled {
return [WXApi isWXAppInstalled];
}
- (BOOL)isWeChatSupportApi {
return [WXApi isWXAppSupportApi];
}
#pragma mark - ————— WXApiDelegate —————
- (void)onResp:(BaseResp *)resp {
if ([resp isKindOfClass:[SendMessageToWXResp class]]) {
if (self.shareCompletion) {
if (resp.errCode == WXSuccess) {
self.shareCompletion(YES, nil);
} else if (resp.errCode == WXErrCodeUserCancel) {
self.shareCompletion(NO, @"用户取消分享");
} else {
self.shareCompletion(NO, resp.errStr ?: @"分享失败");
}
self.shareCompletion = nil;
}
}
}
@end
@@ -55,7 +55,7 @@
if ([HQCommonUtils isLogin]) {
//腾讯im
#if DEBUG
// [self loginImAccount];
[self loginImAccount];
#else
[self loginImAccount];
@@ -7,6 +7,7 @@
#import "AppDelegate.h"
#import "SJRotationManager.h"
#import "XJWeChatManager.h"
#import <SDWebImageWebPCoder/SDWebImageWebPCoder.h>
@implementation AppDelegate
@@ -34,8 +35,10 @@
SDImageWebPCoder*webPCoder = [SDImageWebPCoder sharedCoder];
[[SDImageCodersManager sharedManager] addCoder:webPCoder];
// 注册微信 SDK
[[XJWeChatManager sharedManager] registerWithAppID:kAppKey_Wechat
universalLink:kUniversalLink_Wechat];
return YES;
}
@@ -56,6 +59,17 @@
}
- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {
if (window == self.window) {
// 遍历找到最顶层的 presented VC
UIViewController *vc = self.window.rootViewController;
while (vc.presentedViewController) { vc = vc.presentedViewController; }
// WKWebView 视频全屏(WKFullScreenViewController)或 AVPlayer 全屏时放行横屏
NSString *className = NSStringFromClass(vc.class);
if ([className containsString:@"FullScreen"] || [className containsString:@"AVPlayer"]) {
return UIInterfaceOrientationMaskAllButUpsideDown;
}
return UIInterfaceOrientationMaskPortrait;
}
return [SJRotationManager supportedInterfaceOrientationsForWindow:window];
}
@@ -74,10 +88,15 @@
}
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
return [[XJWeChatManager sharedManager] handleOpenURL:url];
}
- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void(^)(NSArray<id<UIUserActivityRestoring>> * __nullable restorableObjects))restorationHandler {
if ([userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) {
return [[XJWeChatManager sharedManager] handleOpenUniversalLink:userActivity];
}
return YES;
}
@@ -14,6 +14,10 @@
//@property (nonatomic, copy)NSString *videoUrl;
//@property (nonatomic, copy)NSString *messageType;
/// 网页加载进度条
@property (nonatomic, strong) UIProgressView *progressView;
/// 加载失败占位视图
@property (nonatomic, strong) UIView *errorView;
@end
@@ -42,7 +46,6 @@
@implementation XJBaseWebViewVC
#pragma mark - LifeCycle
- (void)viewDidLoad {
[super viewDidLoad];
@@ -61,6 +64,8 @@
}
- (void)dealloc {
// 移除进度条 KVO
[self.wkWebView removeObserver:self forKeyPath:@"estimatedProgress"];
// 移除所有 scriptMessageHandler,配合 XJWeakScriptMessageHandler 彻底避免内存泄漏
WKUserContentController *ucc = self.wkWebView.configuration.userContentController;
[ucc removeScriptMessageHandlerForName:@"JSCallback"];
@@ -101,7 +106,8 @@
preferences.javaScriptCanOpenWindowsAutomatically = YES;
preferences.minimumFontSize = 10;
configuration.preferences = preferences;
// 允许视频内联播放,避免全屏时布局计算错误导致显示不完整
configuration.allowsInlineMediaPlayback = YES;
// ── 统一使用 configuration 自带的 userContentController ──────────────────
// Bug 修复:原先在下方又新建了 wkUController 并覆盖 configuration.userContentController
// 导致此处注册的 JSCallback / htmlBack 被丢弃,永远收不到 JS 消息。
@@ -131,8 +137,27 @@
// configuration.userContentController = wkUController;
[configuration.userContentController addUserScript:wkUScript];
// 禁用网页长按文字选中和复制粘贴菜单
NSString *noSelectJS = @"document.documentElement.style.webkitUserSelect='none';"
"document.documentElement.style.webkitTouchCallout='none';";
WKUserScript *noSelectScript = [[WKUserScript alloc] initWithSource:noSelectJS
injectionTime:WKUserScriptInjectionTimeAtDocumentEnd
forMainFrameOnly:NO];
[configuration.userContentController addUserScript:noSelectScript];
self.wkWebView = [[WKWebView alloc]initWithFrame:CGRectMake(0, 0, WIDTH, MyViewHEIGHT) configuration:configuration];
if (@available(iOS 16.4, *)) {
self.wkWebView.inspectable = YES;
}
// 伪装成 Safari User-Agent,排查 H5 API 请求因 UA 差异被拒的问题
{
NSString *osVer = [UIDevice currentDevice].systemVersion;
NSString *osVerUnder = [osVer stringByReplacingOccurrencesOfString:@"." withString:@"_"];
self.wkWebView.customUserAgent = [NSString stringWithFormat:
@"Mozilla/5.0 (iPhone; CPU iPhone OS %@ like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/%@ Mobile/15E148 Safari/604.1",
osVerUnder, osVer];
}
self.wkWebView.navigationDelegate = self;
self.wkWebView.UIDelegate = self;
if (@available(iOS 11.0, *)) {
@@ -140,17 +165,78 @@
}
[self.view addSubview:self.wkWebView];
self.wkWebView.scrollView.bounces = false;//禁止滑动
[self webViewRequest];
/*
[self.wkWebView evaluateJavaScript:@"htmlBack" completionHandler:^(id _Nullable response, NSError * _Nullable error) {
NSLog(@"response: %@ error: %@", response, error);
if (response) {
[self back];
}
}];
*/
// 进度条,居中偏上
self.progressView = [[UIProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleDefault];
self.progressView.trackTintColor = [UIColor clearColor];
[self.view addSubview:self.progressView];
[self.progressView mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.equalTo(self.wkWebView);
make.centerY.equalTo(self.wkWebView).offset(-60);
make.width.mas_offset(200);
make.height.mas_offset(2);
}];
// KVO 监听加载进度
[self.wkWebView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew context:nil];
// 加载失败占位视图(默认隐藏)
self.errorView = [[UIView alloc] init];
self.errorView.backgroundColor = [UIColor whiteColor];
self.errorView.hidden = YES;
[self.view addSubview:self.errorView];
[self.errorView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.wkWebView);
}];
UILabel *tipLabel = [[UILabel alloc] init];
tipLabel.text = @"网络加载失败\n请检查网络后重试";
tipLabel.textAlignment = NSTextAlignmentCenter;
tipLabel.numberOfLines = 0;
tipLabel.textColor = [UIColor colorWithRed:0.6 green:0.6 blue:0.6 alpha:1.0];
tipLabel.font = [UIFont systemFontOfSize:15];
[self.errorView addSubview:tipLabel];
[tipLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.equalTo(self.errorView);
make.centerY.equalTo(self.errorView).offset(-40);
make.left.greaterThanOrEqualTo(self.errorView).offset(20);
make.right.lessThanOrEqualTo(self.errorView).offset(-20);
}];
UIButton *reloadBtn = [UIButton buttonWithType:UIButtonTypeCustom];
[reloadBtn setTitle:@"重新加载" forState:UIControlStateNormal];
[reloadBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
reloadBtn.titleLabel.font = [UIFont systemFontOfSize:15];
reloadBtn.backgroundColor = [UIColor colorWithRed:0.0 green:0.68 blue:0.41 alpha:1.0];
reloadBtn.layer.cornerRadius = 20;
reloadBtn.layer.masksToBounds = YES;
[reloadBtn addTarget:self action:@selector(reloadWebView) forControlEvents:UIControlEventTouchUpInside];
[self.errorView addSubview:reloadBtn];
[reloadBtn mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(tipLabel.mas_bottom).offset(24);
make.right.equalTo(self.errorView.mas_centerX).offset(-8);
make.width.mas_offset(110);
make.height.mas_offset(40);
}];
UIButton *backBtn = [UIButton buttonWithType:UIButtonTypeCustom];
[backBtn setTitle:@"返回" forState:UIControlStateNormal];
[backBtn setTitleColor:[UIColor colorWithRed:0.0 green:0.68 blue:0.41 alpha:1.0] forState:UIControlStateNormal];
backBtn.titleLabel.font = [UIFont systemFontOfSize:15];
backBtn.backgroundColor = [UIColor whiteColor];
backBtn.layer.cornerRadius = 20;
backBtn.layer.masksToBounds = YES;
backBtn.layer.borderWidth = 1;
backBtn.layer.borderColor = [UIColor colorWithRed:0.0 green:0.68 blue:0.41 alpha:1.0].CGColor;
[backBtn addTarget:self action:@selector(backVC) forControlEvents:UIControlEventTouchUpInside];
[self.errorView addSubview:backBtn];
[backBtn mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(reloadBtn);
make.left.equalTo(self.errorView.mas_centerX).offset(8);
make.width.height.equalTo(reloadBtn);
}];
[self webViewRequest];
}
- (void)finishActivity {
@@ -161,12 +247,22 @@
}
- (void)reloadWebView {
self.errorView.hidden = YES;
[self webViewRequest];
}
- (void)webViewRequest{
[self clearWebViewCache];
NSString *urlString = self.webUrl;
DLog(@"[WebView] 请求加载: %@", urlString);
[self hiddenAllMBProgressHUD];
if (urlString.length > 0) {
[self showMBProgressHUDOnlyWithString:@"加载中"];
NSURL *url = [NSURL URLWithString:urlString];
if (!url) {
DLog(@"[WebView] URL 无效: %@", urlString);
}
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
if (self.wkWebView) {
[self.wkWebView loadRequest:urlRequest];
@@ -177,6 +273,24 @@
#pragma mark - KVO
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([keyPath isEqualToString:@"estimatedProgress"]) {
float progress = [change[NSKeyValueChangeNewKey] floatValue];
self.progressView.alpha = 1.0;
[self.progressView setProgress:progress animated:YES];
if (progress >= 1.0) {
// 加载完成后渐隐进度条
[UIView animateWithDuration:0.3 delay:0.2 options:0 animations:^{
self.progressView.alpha = 0;
} completion:^(BOOL finished) {
[self.progressView setProgress:0 animated:NO];
}];
}
}
}
#pragma mark - WKNavigationDelegate
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
if ([message.name isEqualToString:@"JSCallback"]) {
@@ -220,14 +334,17 @@
}
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation{
DLog(@"[WebView] 开始加载: %@", webView.URL);
self.errorView.hidden = YES;
if (!self.webTitle) {
self.title = @"加载中...";
}
}
- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation{
DLog(@"[WebView] 加载完成: %@", webView.URL);
[self hiddenAllMBProgressHUD];
self.errorView.hidden = YES;
if (!self.webTitle) {
self.title = webView.title;
}
@@ -235,7 +352,23 @@
}
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(nonnull NSError *)error{
NSLog(@"[WebView] 加载失败 URL=%@ errorCode=%ld domain=%@ desc=%@",
webView.URL, (long)error.code, error.domain, error.localizedDescription);
[self hiddenAllMBProgressHUD];
// NSURLErrorCancelled (-999) 是主动取消导航(如跳转第三方 App),不算真实网络错误
if (error.code != NSURLErrorCancelled) {
self.errorView.hidden = NO;
}
}
- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error {
NSLog(@"[WebView] 导航失败 URL=%@ errorCode=%ld domain=%@ desc=%@",
webView.URL, (long)error.code, error.domain, error.localizedDescription);
[self hiddenAllMBProgressHUD];
// NSURLErrorCancelled (-999) 是主动取消导航,不算真实网络错误
if (error.code != NSURLErrorCancelled) {
self.errorView.hidden = NO;
}
}
#pragma mark - 白屏修复
@@ -39,6 +39,7 @@ NS_ASSUME_NONNULL_BEGIN
//地图
@property (nonatomic, assign) CGFloat Longitude;
@property (nonatomic, assign) CGFloat Latitude;
@property (nonatomic, copy) NSString * address;//地址描述
@end
@@ -6,14 +6,12 @@
//
#import <TIMCommon/TUIBubbleMessageCell.h>
#import <TIMCommon/TUIMessageCell.h>
#import "CustomMapMessageCellData.h"
#import "CustomTUIHistoryModel.h"
@interface CustomMapMessageCell : UITableViewCell
@interface CustomMapMessageCell : TUIBubbleMessageCell
@property (nonatomic, strong) CustomTUIHistoryModel * model;
@end
@@ -6,224 +6,161 @@
//
#import "CustomMapMessageCell.h"
#import "XJWeChatManager.h"
#import <SDWebImage/SDImageCache.h>
#import<CommonCrypto/CommonDigest.h>
@interface CustomMapMessageCell ()
@property (nonatomic, strong) UIImageView * heardImage;
@property (nonatomic, strong) UILabel * nickNameLabel;
@property (nonatomic, strong) UILabel * myTextLabel; // 展示文本
@property (nonatomic, strong) UIImageView * mapImage; //
@property (nonatomic, strong) UIView * backView;
@property (nonatomic, strong) UILabel * myTextLabel;
@property (nonatomic, strong) UIImageView * mapImage;
@end
@implementation CustomMapMessageCell
- (void)awakeFromNib {
[super awakeFromNib];
// Initialization code
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
[self createUI];
[self createMapUI];
}
return self;
}
- (void)createUI {
self.heardImage = [[UIImageView alloc] init];
self.heardImage.backgroundColor = KWhiteColor;
self.heardImage.image = [UIImage imageNamed:@"heard_man"];
[self.contentView addSubview:self.heardImage];
self.heardImage.layer.cornerRadius = 20;
self.heardImage.layer.masksToBounds = true;
[self.heardImage mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(@15);
make.top.mas_equalTo(@8);
make.height.width.mas_equalTo(@30);
}];
self.nickNameLabel = [[UILabel alloc] init];
self.nickNameLabel.text = @"全科医生";
[self.contentView addSubview:self.nickNameLabel];
self.nickNameLabel.font = SYSTEMFONT(12);
[self.nickNameLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(self.heardImage.mas_right).offset(10);
make.top.mas_equalTo(@10);
make.width.mas_equalTo(@300.0);
make.height.mas_offset(13);
}];
//姓名
self.backView = [[UIView alloc] init];
self.backView.layer.cornerRadius = 10;
self.backView.backgroundColor = KWhiteColor;
[self.contentView addSubview:self.backView];
[self.backView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(self.heardImage.mas_right).offset(10);
make.width.mas_offset(245);
make.top.mas_equalTo(self.nickNameLabel.mas_bottom).offset(8);
make.height.mas_offset(170);
}];
- (void)createMapUI {
// 隐藏默认气泡图片,改用白色圆角
self.bubbleView.image = nil;
self.myTextLabel = [[UILabel alloc] init];
self.myTextLabel.font = SYSTEMFONT(15);
self.myTextLabel.textColor = CFontColor1;
self.myTextLabel.text = @"我的位置";
[self.backView addSubview:self.myTextLabel];
self.myTextLabel.numberOfLines = 0;
[self.container addSubview:self.myTextLabel];
[self.myTextLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_offset(10);
make.top.mas_offset(0);
make.top.mas_offset(10);
make.right.mas_offset(-10);
make.height.mas_offset(25);
}];
}];
self.mapImage = [[UIImageView alloc] init];
self.mapImage.backgroundColor = KRedColor;
[self.backView addSubview:self.mapImage];
[self.container addSubview:self.mapImage];
[self.mapImage mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.bottom.mas_offset(0);
make.top.mas_equalTo(self.myTextLabel.mas_bottom);
}];
// 长按转发到微信
// TUIChat 框架把长按手势注释掉了,这里重新加上。
// 需要让 container 上已有的 tap 手势等 longPress 失败后再触发,否则 tap 会抢走触摸。
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(onLongPress:)];
longPress.minimumPressDuration = 0.5;
[self.container addGestureRecognizer:longPress];
for (UIGestureRecognizer *g in self.container.gestureRecognizers) {
if ([g isKindOfClass:[UITapGestureRecognizer class]]) {
[g requireGestureRecognizerToFail:longPress];
}
}
}
#pragma mark - TUIChat 实时聊天回调
- (void)fillWithData:(CustomMapMessageCellData *)data {
[super fillWithData:data];
self.myTextLabel.text = data.text.length > 0 ? data.text : @"我的位置";
[self.mapImage sd_setImageWithURL:[NSURL URLWithString:data.MapPictureImg] placeholderImage:nil];
self.nameLabel.hidden = YES;
}
#pragma mark - 历史记录回调
- (void)setModel:(CustomTUIHistoryModel *)model {
_model = model;
// self.nickNameLabel.text = _model.fromAccountName;
if (_model.isSelf == true) { //头像在右
[self.heardImage mas_remakeConstraints:^(MASConstraintMaker *make) {
self.myTextLabel.text = _model.address.length > 0 ? _model.address : @"我的位置";
NSString * MapPictureImg = [self mapLocationImage:_model.Longitude Withlation:_model.Latitude];
[self.mapImage sd_setImageWithURL:[NSURL URLWithString:MapPictureImg] placeholderImage:nil];
// 历史记录:手动设置头像和名字
self.avatarView.image = [UIImage imageNamed:@"heard_man"];
self.nameLabel.text = @"全科医生";
self.nameLabel.hidden = NO;
if (_model.isSelf) {
self.avatarView.hidden = NO;
[self.avatarView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.height.width.mas_offset(40);
make.top.mas_equalTo(@10);
make.right.mas_equalTo(self.contentView.mas_right).offset(-10);
}];
self.nickNameLabel.textAlignment = NSTextAlignmentRight;
[self.nickNameLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
make.right.mas_equalTo(self.heardImage.mas_left).offset(-10);
self.nameLabel.textAlignment = NSTextAlignmentRight;
[self.nameLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
make.right.mas_equalTo(self.avatarView.mas_left).offset(-10);
make.top.mas_equalTo(@10);
make.width.mas_equalTo(@245.0);
make.height.mas_offset(13);
}];
[self.backView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(self.nickNameLabel.mas_bottom).offset(8);
[self.container mas_remakeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(self.nameLabel.mas_bottom).offset(8);
make.width.mas_offset(245);
make.right.mas_equalTo(self.heardImage.mas_left).offset(-10);
make.height.mas_equalTo(@170);
make.right.mas_equalTo(self.avatarView.mas_left).offset(-10);
make.height.mas_offset(170);
}];
}else
{
[self.heardImage mas_remakeConstraints:^(MASConstraintMaker *make) {
} else {
self.avatarView.hidden = NO;
[self.avatarView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(@15);
make.top.mas_equalTo(@8);
make.height.width.mas_equalTo(@40);
}];
self.nickNameLabel.textAlignment = NSTextAlignmentLeft;
[self.nickNameLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(self.heardImage.mas_right).offset(10);
self.nameLabel.textAlignment = NSTextAlignmentLeft;
[self.nameLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(self.avatarView.mas_right).offset(10);
make.top.mas_equalTo(@10);
make.width.mas_equalTo(@300.0);
make.height.mas_offset(13);
}];
[self.backView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(self.heardImage.mas_right).offset(10);
[self.container mas_remakeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(self.avatarView.mas_right).offset(10);
make.width.mas_offset(245);
make.top.mas_equalTo(self.nickNameLabel.mas_bottom).offset(8);
make.top.mas_equalTo(self.nameLabel.mas_bottom).offset(8);
make.height.mas_offset(170);
}];
}
[self.contentView mas_updateConstraints:^(MASConstraintMaker *make) {
make.top.left.right.mas_equalTo(@0);
make.bottom.mas_equalTo(self.backView.mas_bottom).offset(10);
}];
NSString * MapPictureImg = [self mapLocationImage:_model.Longitude Withlation:_model.Latitude];
[self.mapImage sd_setImageWithURL:[NSURL URLWithString:MapPictureImg] placeholderImage:nil];
NSString * photo = [NSString stringWithFormat:@"%@%@",ResourceAddress,_model.avatar];
NSString * str1 = @"\\";
photo = [photo stringByReplacingOccurrencesOfString:str1 withString:@"/"];
// 加载头像
NSString * photo = [NSString stringWithFormat:@"%@%@", ResourceAddress, _model.avatar];
photo = [photo stringByReplacingOccurrencesOfString:@"\\" withString:@"/"];
NSString *encodedString3 = CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)photo, (CFStringRef)@"!$&'()*+,-./:;=?@_~%#[]", NULL, kCFStringEncodingUTF8));
[self.heardImage sd_setImageWithURL:[NSURL URLWithString:encodedString3] placeholderImage:PLACEHOLD_IMG];
[self.avatarView sd_setImageWithURL:[NSURL URLWithString:encodedString3] placeholderImage:PLACEHOLD_IMG];
}
#pragma mark - 地图静态图
- (NSString *)mapLocationImage:(CGFloat)longLation Withlation:(CGFloat)lation {
// 0: self.longLation,self.lation
//https://a.amap.com/jsapi_demos/static/demo-center/icons/poi-marker-default.png
NSString * staciUrl = @"https://restapi.amap.com/v3/staticmap?key=e9926badd26aaa766d13439c88a83f2d";
NSString * mapKey = @"e9926badd26aaa766d13439c88a83f2d";
NSString * loctaionUrl = @"https://a.amap.com/jsapi_demos/static/demo-center/icons/poi-marker-default.png";
NSString * needMdString = [NSString stringWithFormat:@"key=%@&location=%f,%f&markers=-1,%@,0:%f,%f&size=700*500&zoom=15124d58f7223ac9738312a8d187a21f2b",mapKey,longLation,lation,loctaionUrl,longLation,lation];
NSString * mapUrl = [NSString stringWithFormat:@"%@&location=%f,%f&markers=-1,%@,0:%f,%f&size=700*500&zoom=15&sig=%@",staciUrl,longLation,lation,loctaionUrl,longLation,lation,[self md5:needMdString]];
return mapUrl;
}
//md加密
- (NSString *)md5:(NSString *)str
{
const char *cStr = [str UTF8String];
unsigned char result[16];
CC_MD5(cStr, strlen(cStr), result); // This is the md5 call
CC_MD5(cStr, strlen(cStr), result);
return [NSString stringWithFormat:
@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
result[0], result[1], result[2], result[3],
@@ -233,8 +170,83 @@
];
}
#pragma mark - 长按转发微信
- (void)onLongPress:(UILongPressGestureRecognizer *)gesture {
if (gesture.state != UIGestureRecognizerStateBegan) return;
if (![[XJWeChatManager sharedManager] isWeChatInstalled]) {
[EasyTextView showErrorText:@"未安装微信"];
return;
}
UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet];
[alert addAction:[UIAlertAction actionWithTitle:@"转发到微信" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
[self shareToWeChat];
}]];
[alert addAction:[UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:nil]];
UIViewController *topVC = [self topViewController];
[topVC presentViewController:alert animated:YES completion:nil];
}
- (void)shareToWeChat {
NSString *address;
CGFloat lat = 0, lng = 0;
NSString *imageKey;
// 实时聊天数据
CustomMapMessageCellData *data = (CustomMapMessageCellData *)self.messageData;
if ([data isKindOfClass:[CustomMapMessageCellData class]]) {
address = data.text.length > 0 ? data.text : @"我的位置";
lat = data.latitude;
lng = data.longitude;
imageKey = data.MapPictureImg;
}
// 历史记录数据
else if (_model) {
address = _model.address.length > 0 ? _model.address : @"我的位置";
lat = _model.Latitude;
lng = _model.Longitude;
imageKey = [self mapLocationImage:_model.Longitude Withlation:_model.Latitude];
} else {
return;
}
NSString *encodedAddress = [address stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
NSString *mapUrl = [NSString stringWithFormat:@"https://uri.amap.com/marker?position=%f,%f&name=%@",
lng, lat, encodedAddress ?: @"位置"];
UIImage *thumb = [[SDImageCache sharedImageCache] imageFromCacheForKey:imageKey];
[[XJWeChatManager sharedManager] shareWebpageWithTitle:address
desc:[NSString stringWithFormat:@"经度:%.6f 纬度:%.6f", lng, lat]
webpageUrl:mapUrl
thumbImage:thumb
toScene:0
completion:^(BOOL success, NSString * _Nullable errorMsg) {
if (!success && errorMsg.length > 0) {
[EasyTextView showErrorText:errorMsg];
}
}];
}
- (UIViewController *)topViewController {
UIViewController *rootVC = [UIApplication sharedApplication].keyWindow.rootViewController;
while (rootVC.presentedViewController) {
rootVC = rootVC.presentedViewController;
}
if ([rootVC isKindOfClass:[UINavigationController class]]) {
return [(UINavigationController *)rootVC topViewController];
}
if ([rootVC isKindOfClass:[UITabBarController class]]) {
UIViewController *selected = [(UITabBarController *)rootVC selectedViewController];
if ([selected isKindOfClass:[UINavigationController class]]) {
return [(UINavigationController *)selected topViewController];
}
return selected;
}
return rootVC;
}
@end
@@ -13,6 +13,7 @@
@property (nonatomic, copy) NSString *text;
@property (nonatomic, copy) NSString * MapPictureImg;
@property (nonatomic, assign) CGFloat latitude;
@property (nonatomic, assign) CGFloat longitude;
@end
@@ -16,6 +16,8 @@
cellData.innerMessage = message;
cellData.msgID = message.msgID;
cellData.text = message.locationElem.desc;
cellData.latitude = message.locationElem.latitude;
cellData.longitude = message.locationElem.longitude;
cellData.MapPictureImg = [self mapLocationImage:message.locationElem.longitude Withlation:message.locationElem.latitude];
cellData.reuseId = @"MapMessageCustomCell";
cellData.showAvatar = true;
@@ -15,17 +15,33 @@
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.isHidenNaviBar = true;
[self.wkWebView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.top.left.right.mas_offset(0);
make.bottom.mas_offset(-SafetyAreaHeight);
}];
}
/*
#pragma mark - Navigation
#pragma mark - WKNavigationDelegate
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation {
[super webView:webView didStartProvisionalNavigation:navigation];
DLog(@"[健康H5] 开始加载: %@", webView.URL.absoluteString);
}
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
[super webView:webView didFinishNavigation:navigation];
DLog(@"[健康H5] 加载完成: %@", webView.URL.absoluteString);
}
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error {
[super webView:webView didFailProvisionalNavigation:navigation withError:error];
DLog(@"[健康H5] 加载失败: %@", error.localizedDescription);
}
- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error {
[super webView:webView didFailNavigation:navigation withError:error];
DLog(@"[健康H5] 导航失败: %@", error.localizedDescription);
}
*/
@end
@@ -25,13 +25,4 @@
// finishActivity 行为与基类一致(pop 返回),无需 override
// 基类 XJBaseWebViewVC 已实现默认 pop 逻辑
/*
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
@end
@@ -0,0 +1,16 @@
//
// XJHealthArchiveDetailViewController.h
// XinjiangProject
//
// 一人一案 - 健康档案查看页
//
#import "XJBaseViewController.h"
NS_ASSUME_NONNULL_BEGIN
@interface XJHealthArchiveDetailViewController : XJBaseViewController
@end
NS_ASSUME_NONNULL_END
@@ -32,6 +32,7 @@
#import "XJHomePageV2QuestionTableView.h"
#import "MyMessageListViewController.h"
#import "EmptyFunctionDevelopmentVC.h"
#import "XJHealthArchiveDetailViewController.h"
#import "XJSOSLookDoctoreViewController.h"
#import "HomeBannerModel.h"
#import "SDCycleScrollView.h"
@@ -1121,15 +1122,16 @@ static CGFloat const kEmptyTableMinHeight = 250; // 或屏幕高度的一半
}];
bottomTools.imgeBlock = ^(NSInteger imgIndex) {
EmptyFunctionDevelopmentVC * vc = [[EmptyFunctionDevelopmentVC alloc] init];
if (imgIndex == 1) {
EmptyFunctionDevelopmentVC * vc = [[EmptyFunctionDevelopmentVC alloc] init];
vc.navigationItem.title = @"智慧体重";
[self.navigationController pushViewController:vc animated:YES];
}
if (imgIndex == 2) {
vc.navigationItem.title = @"一人一案";
XJHealthArchiveDetailViewController * vc = [[XJHealthArchiveDetailViewController alloc] init];
[self.navigationController pushViewController:vc animated:YES];
}
[self.navigationController pushViewController:vc animated:YES];
};
// 健康知识模块
@@ -1145,12 +1147,20 @@ static CGFloat const kEmptyTableMinHeight = 250; // 或屏幕高度的一半
}];
healthKnowledgeView.moreBlock = ^{
if (![HQCommonUtils isLogin]) {
KPostNotification(KNotificationLoginStateChange, @NO);
return;
}
[weakself ensureTokenThenExecute:^{
[weakself openHealthKnowledgeMorePage];
}];
};
healthKnowledgeView.searchBlock = ^(NSString * _Nonnull keyword) {
if (![HQCommonUtils isLogin]) {
KPostNotification(KNotificationLoginStateChange, @NO);
return;
}
[weakself ensureTokenThenExecute:^{
[weakself openHealthKnowledgeSearchPage:keyword];
}];
@@ -1180,6 +1190,10 @@ static CGFloat const kEmptyTableMinHeight = 250; // 或屏幕高度的一半
}];
healthNewsView.moreBlock = ^{
if (![HQCommonUtils isLogin]) {
KPostNotification(KNotificationLoginStateChange, @NO);
return;
}
[weakself ensureTokenThenExecute:^{
[weakself openHealthNewsMorePage];
}];
@@ -1298,10 +1312,6 @@ static CGFloat const kEmptyTableMinHeight = 250; // 或屏幕高度的一半
}
if (type == 1) {
// EmptyFunctionDevelopmentVC * vc = [[EmptyFunctionDevelopmentVC alloc] init];
// vc.navigationItem.title = @"健康体检";
// [self.navigationController pushViewController:vc animated:YES];
if(![HQCommonUtils isLogin]) {
KPostNotification(KNotificationLoginStateChange, @NO);
return;
@@ -1543,6 +1553,7 @@ static CGFloat const kEmptyTableMinHeight = 250; // 或屏幕高度的一半
NSString * userId = User_ID;
if (ValidStr(userId)) {
DLog(@"[健康知识] User_ID 为空,跳过获取 token");
[self.mainScrollView.mj_header endRefreshing];
return;
}
@@ -1555,6 +1566,7 @@ static CGFloat const kEmptyTableMinHeight = 250; // 或屏幕高度的一半
[weakself loadHealthCategoriesWithToken:t];
} failure:^(NSString * errorMsg) {
DLog(@"健康知识 token 获取失败: %@", errorMsg);
[weakself.mainScrollView.mj_header endRefreshing];
}];
}
@@ -1576,6 +1588,7 @@ static CGFloat const kEmptyTableMinHeight = 250; // 或屏幕高度的一半
}
dispatch_async(dispatch_get_main_queue(), ^{
BOOL wasLoaded = weakself.healthDataLoaded;
weakself.healthDataLoaded = YES;
// 健康知识
@@ -1589,13 +1602,25 @@ static CGFloat const kEmptyTableMinHeight = 250; // 或屏幕高度的一半
DLog(@"[健康知识] 标签: %@", titles);
weakself.healthKnowledgeView.tabTitles = titles;
weakself.currentHealthCategory = knowledgeCategory.children.firstObject;
// 非首次加载时保留用户选中的标签
if (wasLoaded) {
NSInteger idx = weakself.healthKnowledgeView.selectedTabIndex;
if (idx >= 0 && idx < knowledgeCategory.children.count) {
weakself.currentHealthCategory = knowledgeCategory.children[idx];
} else {
weakself.currentHealthCategory = knowledgeCategory.children.firstObject;
}
} else {
weakself.currentHealthCategory = knowledgeCategory.children.firstObject;
}
DLog(@"[健康知识] 默认选中: %@ (categoryId=%ld)", weakself.currentHealthCategory.categoryName, (long)weakself.currentHealthCategory.categoryId);
[weakself fetchHealthArticlesForCategoryId:weakself.currentHealthCategory.categoryId isNews:NO];
} else {
DLog(@"[健康知识] 栏目不存在或无子分类");
} else if (!wasLoaded) {
DLog(@"[健康知识] 栏目不存在或无子分类,首次加载清空");
weakself.healthKnowledgeView.dataArray = @[];
[weakself updateHealthKnowledgeViewHeight];
} else {
DLog(@"[健康知识] 栏目不存在或无子分类,保留已有数据");
}
// 健康资讯:无子分类,直接用 categoryId 查文章
@@ -1603,14 +1628,18 @@ static CGFloat const kEmptyTableMinHeight = 250; // 或屏幕高度的一半
weakself.healthNewsCategoryId = newsCategory.categoryId;
DLog(@"[健康资讯] 找到栏目 categoryId=%ld, children=%lu", (long)newsCategory.categoryId, (unsigned long)newsCategory.children.count);
[weakself fetchHealthArticlesForCategoryId:newsCategory.categoryId isNews:YES];
} else {
DLog(@"[健康资讯] 未找到栏目");
} else if (!wasLoaded) {
DLog(@"[健康资讯] 未找到栏目,首次加载清空");
weakself.healthNewsView.dataArray = @[];
[weakself updateHealthNewsViewHeight];
} else {
DLog(@"[健康资讯] 未找到栏目,保留已有数据");
}
[weakself.mainScrollView.mj_header endRefreshing];
});
} failure:^(NSString * errorMsg) {
DLog(@"[健康知识] 栏目列表获取失败: %@", errorMsg);
[weakself.mainScrollView.mj_header endRefreshing];
}];
}
@@ -1623,6 +1652,7 @@ static CGFloat const kEmptyTableMinHeight = 250; // 或屏幕高度的一半
NSString * token = [XJHealthKnowledgeManager sharedManager].cachedToken;
if (ValidStr(token)) {
DLog(@"[健康知识] fetchArticles token 无效,跳过");
[weakself.mainScrollView.mj_header endRefreshing];
return;
}
@@ -1652,6 +1682,12 @@ static CGFloat const kEmptyTableMinHeight = 250; // 或屏幕高度的一半
DLog(@"[%@] 转换后 dataArray 共 %lu 条", tag, (unsigned long)dataArray.count);
dispatch_async(dispatch_get_main_queue(), ^{
// 非首次加载且返回空数据时,不覆盖已有内容
if (dataArray.count == 0 && weakself.healthDataLoaded) {
DLog(@"[%@] 返回空数据但已加载过,保留已有内容", tag);
[weakself.mainScrollView.mj_header endRefreshing];
return;
}
if (isNews) {
weakself.healthNewsView.dataArray = dataArray;
[weakself updateHealthNewsViewHeight];
@@ -1659,9 +1695,11 @@ static CGFloat const kEmptyTableMinHeight = 250; // 或屏幕高度的一半
weakself.healthKnowledgeView.dataArray = dataArray;
[weakself updateHealthKnowledgeViewHeight];
}
[weakself.mainScrollView.mj_header endRefreshing];
});
} failure:^(NSString * errorMsg) {
DLog(@"[%@] 文章列表获取失败: %@", tag, errorMsg);
[weakself.mainScrollView.mj_header endRefreshing];
}];
}
@@ -1751,6 +1789,7 @@ static CGFloat const kEmptyTableMinHeight = 250; // 或屏幕高度的一半
}
- (void)openHealthKnowledgeDetailPage:(XJHealthKnowledgeModel *)model {
[self showMBProgressHUDOnlyWithString:@"加载中"];
NSString * param = !ValidStr(self.secretString) ? self.secretString : @"";
NSString * url = [NSString stringWithFormat:@"%@%@?param=%@&articleId=%@",
kHealthKnowledgeH5Host, kHealthDetailH5, param, model.knowledgeId];
@@ -229,21 +229,25 @@
- (void)updateUI {
if (self.type == BindWatchTypeUnbind) {
[self.actionBtn setTitle:@"解绑" forState:UIControlStateNormal];
// 解绑页面:显示已绑定SN,按钮改为"绑定"并禁用(30%透明度,不可点击)
[self.actionBtn setTitle:@"绑定" forState:UIControlStateNormal];
self.actionBtn.alpha = 0.3;
self.actionBtn.userInteractionEnabled = NO;
self.numberField.text = self.serialNumber;
self.numberField.backgroundColor = UIColorHex(#F5F7FB);
self.numberField.enabled = NO;
} else {
// 绑定页面:正常可点击
[self.actionBtn setTitle:@"绑定" forState:UIControlStateNormal];
self.actionBtn.alpha = 1.0;
self.actionBtn.userInteractionEnabled = YES;
self.numberField.text = @"";
self.numberField.enabled = YES;
self.numberField.backgroundColor = KWhiteColor;
}
}
@@ -146,7 +146,7 @@
self.titleLabel.text = model.title;
self.descLabel.text = model.content;
self.timeLabel.text = model.createTime;
self.timeLabel.text = [self dateString:model.createTime];
// 标签 #健康资讯#肝癌 → 健康资讯 | 肝癌
self.tagLabel.text = [self formatTagString:model.tags];
@@ -174,4 +174,11 @@
return result;
}
/// 截取日期部分 "2026-04-23 15:28:57" → "2026-04-23",容错处理
- (NSString *)dateString:(NSString *)datetime {
if (!datetime || ![datetime isKindOfClass:[NSString class]] || datetime.length == 0) return @"";
NSArray *parts = [datetime componentsSeparatedByString:@" "];
return parts.firstObject ?: datetime;
}
@end
@@ -197,7 +197,7 @@ static CGFloat const kTabAreaHeight = 48;
// 空状态 - 放在 tabScrollView 下方
self.emptyLabel = [[UILabel alloc] init];
self.emptyLabel.text = @"暂无健康知识";
self.emptyLabel.textColor = [UIColor colorWithRed:0.6 green:0.6 blue:0.6 alpha:1.0];
self.emptyLabel.textColor = UIColorHex(#14BEBE);
self.emptyLabel.font = [UIFont systemFontOfSize:15];
self.emptyLabel.textAlignment = NSTextAlignmentCenter;
self.emptyLabel.hidden = YES;
@@ -315,6 +315,7 @@ static CGFloat const kTabAreaHeight = 48;
self.dataArray = @[];
self.tabTitles = nil;
self.emptyLabel.text = @"暂未登录";
self.emptyLabel.textColor = [UIColor colorWithRed:0.6 green:0.6 blue:0.6 alpha:1.0];
self.emptyLabel.hidden = NO;
// 未登录时在头部下方区域上下居中
[self.emptyLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
@@ -109,7 +109,7 @@ static CGFloat const kDefaultEmptyHeight = 130;
// 空状态
self.emptyLabel = [[UILabel alloc] init];
self.emptyLabel.text = @"暂无健康资讯";
self.emptyLabel.textColor = [UIColor colorWithRed:0.6 green:0.6 blue:0.6 alpha:1.0];
self.emptyLabel.textColor = UIColorHex(#14BEBE);
self.emptyLabel.font = [UIFont systemFontOfSize:15];
self.emptyLabel.textAlignment = NSTextAlignmentCenter;
self.emptyLabel.hidden = YES;
@@ -150,6 +150,7 @@ static CGFloat const kDefaultEmptyHeight = 130;
- (void)showNotLoggedInPlaceholder {
self.dataArray = @[];
self.emptyLabel.text = @"暂未登录";
self.emptyLabel.textColor = [UIColor colorWithRed:0.6 green:0.6 blue:0.6 alpha:1.0];
self.emptyLabel.hidden = NO;
[self.emptyLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
make.centerX.mas_equalTo(self);
@@ -254,7 +254,7 @@
SDCycleScrollView * SDCycles = [SDCycleScrollView cycleScrollViewWithFrame:CGRectZero delegate:self placeholderImage: [UIImage imageNamed:@"placeholder"]];
SDCycles.layer.cornerRadius = 12;
SDCycles.layer.masksToBounds = true;
SDCycles.backgroundColor = KClearColor;
SDCycles.backgroundColor = KWhiteColor;
// UIViewContentModeScaleToFill,
// UIViewContentModeScaleAspectFit, // contents scaled to fit with fixed aspect. remainder is transparent
// UIViewContentModeScaleAspectFill,
@@ -355,7 +355,7 @@
SDCycleScrollView * questionSDCycles = [SDCycleScrollView cycleScrollViewWithFrame:CGRectZero delegate:self placeholderImage:[UIImage imageNamed:@"placeholder"]];
questionSDCycles.backgroundColor = KClearColor;
questionSDCycles.backgroundColor = KWhiteColor;
questionSDCycles.layer.cornerRadius = 12;
questionSDCycles.layer.masksToBounds = true;
questionSDCycles.bannerImageViewContentMode = UIViewContentModeScaleToFill;
@@ -63,6 +63,7 @@
// detailImg.image = [UIImage imageNamed:@"ADDetail"];
self.detailImg = detailImg;
self.detailImg.sd_imageIndicator = [SDWebImageActivityIndicator grayIndicator];
self.detailImg.contentMode = UIViewContentModeScaleAspectFit;
[contentView addSubview:detailImg];
@@ -109,7 +110,8 @@
photo = [ photo stringByReplacingOccurrencesOfString:str1 withString:@"/"];
NSString *encodedString3 = CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef) photo, (CFStringRef)@"!$&'()*+,-./:;=?@_~%#[]", NULL, kCFStringEncodingUTF8));
[[SDImageCache sharedImageCache] removeImageForKey:encodedString3 withCompletion:nil];
[self.detailImg sd_setImageWithURL:[NSURL URLWithString:encodedString3]
placeholderImage:nil
options:SDWebImageRetryFailed
@@ -118,8 +120,14 @@
SDImageCacheType cacheType,
NSURL * _Nullable imageURL) {
if (error) {
self.detailImg.image = [UIImage imageNamed:@"image_load_fail"]; // 失败图
if (!error && image) {
CGFloat ratio = image.size.height / image.size.width;
CGFloat imgHeight = kScreenWidth * ratio;
[self.detailImg mas_updateConstraints:^(MASConstraintMaker *make) {
make.height.mas_equalTo(imgHeight);
}];
} else if (error) {
self.detailImg.image = [UIImage imageNamed:@"image_load_fail"];
}
}];
@@ -0,0 +1,32 @@
//
// XJFirstAidResourceDetailVC.h
// XinjiangProject
//
// 急救宣教资源详情页
// ┌──────────────────────────────┐
// │ NavigationBar │
// ├──────────────────────────────┤
// │ UIScrollView │
// │ ┌────────────────────────┐ │
// │ │ 封面图 (有则展示) │ │
// │ ├────────────────────────┤ │
// │ │ 标题 │ │
// │ ├────────────────────────┤ │
// │ │ 分类 · 浏览 · 时间 │ │
// │ ├────────────────────────┤ │
// │ │ WKWebView 富文本正文 │ │
// │ └────────────────────────┘ │
// └──────────────────────────────┘
//
#import "XJBaseViewController.h"
NS_ASSUME_NONNULL_BEGIN
@interface XJFirstAidResourceDetailVC : XJBaseViewController
@property (nonatomic, copy) NSString * resourceId;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,325 @@
//
// XJFirstAidResourceDetailVC.m
// XinjiangProject
//
#import "XJFirstAidResourceDetailVC.h"
#import "XJFirstAidResourceModel.h"
#import <WebKit/WebKit.h>
#import <Masonry/Masonry.h>
@interface XJFirstAidResourceDetailVC () <WKNavigationDelegate>
@property (nonatomic, strong) UIScrollView * scrollView;
@property (nonatomic, strong) UIView * contentView;
// 内容区
@property (nonatomic, strong) UIImageView * coverImageView;
@property (nonatomic, strong) UILabel * titleLabel;
@property (nonatomic, strong) UILabel * infoLabel; // 分类 · 浏览 · 时间
@property (nonatomic, strong) UIView * infoSeparator;
@property (nonatomic, strong) WKWebView * webView;
// 约束(动态更新 WebView 高度)
@property (nonatomic, strong) MASConstraint * webViewHeightConstraint;
// 数据
@property (nonatomic, strong) XJFirstAidResourceModel * model;
@end
@implementation XJFirstAidResourceDetailVC
#pragma mark - ————— Life Cycle —————
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = CViewBgColor;
self.title = @"文章详情";
[self createUI];
[self loadDetailData];
}
#pragma mark - ————— UI 搭建 —————
- (void)createUI {
// 滚动视图
self.scrollView = [[UIScrollView alloc] init];
self.scrollView.showsVerticalScrollIndicator = NO;
self.scrollView.backgroundColor = CViewBgColor;
[self.view addSubview:self.scrollView];
[self.scrollView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.mas_equalTo(self.view);
}];
// 内容容器
self.contentView = [[UIView alloc] init];
self.contentView.backgroundColor = CViewBgColor;
[self.scrollView addSubview:self.contentView];
[self.contentView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.mas_equalTo(self.scrollView);
make.width.mas_equalTo(self.scrollView);
}];
// 白色卡片
UIView * cardView = [[UIView alloc] init];
cardView.backgroundColor = KWhiteColor;
cardView.layer.cornerRadius = 10;
cardView.layer.masksToBounds = YES;
[self.contentView addSubview:cardView];
[cardView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_offset(12);
make.left.mas_offset(12);
make.right.mas_offset(-12);
make.bottom.mas_equalTo(self.contentView).offset(-20);
}];
// 封面图
self.coverImageView = [[UIImageView alloc] init];
self.coverImageView.contentMode = UIViewContentModeScaleAspectFill;
self.coverImageView.layer.masksToBounds = YES;
self.coverImageView.hidden = YES;
[cardView addSubview:self.coverImageView];
[self.coverImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.left.right.mas_offset(0);
make.height.mas_offset(200);
}];
// 标题
self.titleLabel = [[UILabel alloc] init];
self.titleLabel.font = [UIFont systemFontOfSize:18 weight:UIFontWeightBold];
self.titleLabel.textColor = UIColorHex(0x1A1A1A);
self.titleLabel.numberOfLines = 0;
[cardView addSubview:self.titleLabel];
[self.titleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(self.coverImageView.mas_bottom).offset(16);
make.left.mas_offset(16);
make.right.mas_offset(-16);
}];
// 信息栏(分类 · 浏览 · 时间)
self.infoLabel = [[UILabel alloc] init];
self.infoLabel.font = [UIFont systemFontOfSize:12 weight:UIFontWeightRegular];
self.infoLabel.textColor = UIColorHex(0xB0B0B0);
[cardView addSubview:self.infoLabel];
[self.infoLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(self.titleLabel.mas_bottom).offset(10);
make.left.mas_offset(16);
make.right.mas_offset(-16);
}];
// 分割线
self.infoSeparator = [[UIView alloc] init];
self.infoSeparator.backgroundColor = UIColorHex(0xEAECF1);
[cardView addSubview:self.infoSeparator];
[self.infoSeparator mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(self.infoLabel.mas_bottom).offset(14);
make.left.mas_offset(16);
make.right.mas_offset(-16);
make.height.mas_offset(1);
}];
// WebView 渲染富文本正文
self.webView = [[WKWebView alloc] init];
self.webView.navigationDelegate = self;
self.webView.scrollView.scrollEnabled = NO;
self.webView.opaque = NO;
self.webView.backgroundColor = KWhiteColor;
[cardView addSubview:self.webView];
[self.webView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(self.infoSeparator.mas_bottom).offset(14);
make.left.mas_offset(12);
make.right.mas_offset(-12);
self.webViewHeightConstraint = make.height.mas_offset(1);
make.bottom.mas_equalTo(cardView).offset(-20);
}];
}
#pragma mark - ————— 网络请求 —————
- (void)loadDetailData {
[self showMBProgressHUDWithString:@"加载中..." autoHidden:NO];
NSString * urlStr = [NSString stringWithFormat:@"%@%@?id=%@", Http, kFirstAidResourceDetailPath, self.resourceId];
NSMutableURLRequest * request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:urlStr]];
request.HTTPMethod = @"GET";
[request setValue:@"application/json; charset=utf-8" forHTTPHeaderField:@"Content-Type"];
// 获取 token
HQUserInfo * user = [HQCommonUtils keyedUnarchiverWithKey:LoginUserInfo];
NSString * accessToken = user.token ?: @"";
if (accessToken.length > 0) {
[request setValue:accessToken forHTTPHeaderField:@"X-Access-Token"];
}
NSURLSession * session = [NSURLSession sharedSession];
kWeakSelf(self);
NSURLSessionDataTask * task = [session dataTaskWithRequest:request
completionHandler:^(NSData * _Nullable data,
NSURLResponse * _Nullable response,
NSError * _Nullable error) {
dispatch_async(dispatch_get_main_queue(), ^{
[weakself handleDetailResponse:data error:error response:response];
});
}];
[task resume];
}
- (void)handleDetailResponse:(NSData *)data error:(NSError *)error response:(NSURLResponse *)response {
[self hiddenAllMBProgressHUD];
if (error) {
[EasyTextView showErrorText:@"网络异常,请稍后重试"];
return;
}
NSError * jsonError = nil;
NSDictionary * json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError];
if (jsonError || ![json isKindOfClass:[NSDictionary class]]) {
[EasyTextView showErrorText:@"数据解析失败"];
return;
}
NSDictionary * result = json[@"result"];
if (![result isKindOfClass:[NSDictionary class]]) {
[EasyTextView showErrorText:@"数据为空"];
return;
}
XJFirstAidResourceModel * model = [[XJFirstAidResourceModel alloc] init];
model.resourceId = [NSString stringWithFormat:@"%@", result[@"id"] ?: @""];
model.title = [NSString stringWithFormat:@"%@", result[@"title"] ?: @""];
model.summary = [NSString stringWithFormat:@"%@", result[@"summary"] ?: @""];
model.content = [NSString stringWithFormat:@"%@", result[@"content"] ?: @""];
model.coverImage = [NSString stringWithFormat:@"%@", result[@"coverImage"] ?: @""];
model.contentType = [NSString stringWithFormat:@"%@", result[@"contentType"] ?: @""];
model.categoryName = [NSString stringWithFormat:@"%@", result[@"categoryName"] ?: @""];
model.tags = [NSString stringWithFormat:@"%@", result[@"tags"] ?: @""];
model.viewCount = [result[@"viewCount"] integerValue];
model.favoriteCount = [result[@"favoriteCount"] integerValue];
model.isFavorited = [result[@"isFavorited"] boolValue];
model.createTime = [NSString stringWithFormat:@"%@", result[@"createTime"] ?: @""];
model.fileUrl = [NSString stringWithFormat:@"%@", result[@"fileUrl"] ?: @""];
NSArray * tagList = result[@"tagNameList"];
if ([tagList isKindOfClass:[NSArray class]]) {
model.tagNameList = tagList;
} else {
model.tagNameList = @[];
}
self.model = model;
[self renderContent];
}
#pragma mark - ————— 渲染内容 —————
- (void)renderContent {
self.title = self.model.title;
// 封面图
if (self.model.coverImage.length > 0 && ![self.model.coverImage isEqualToString:@"(null)"]) {
self.coverImageView.hidden = NO;
NSString * encoded = [self encodeURL:self.model.coverImage];
[self.coverImageView sd_setImageWithURL:[NSURL URLWithString:encoded] placeholderImage:nil];
[self.titleLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(self.coverImageView.mas_bottom).offset(16);
make.left.mas_offset(16);
make.right.mas_offset(-16);
}];
} else {
self.coverImageView.hidden = YES;
[self.coverImageView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.top.left.right.mas_offset(0);
make.height.mas_offset(0);
}];
[self.titleLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
make.top.mas_offset(16);
make.left.mas_offset(16);
make.right.mas_offset(-16);
}];
}
// 标题
self.titleLabel.text = self.model.title;
// 信息栏
NSString * infoText = [NSString stringWithFormat:@"%@", self.model.categoryName];
if (self.model.viewCount > 0) {
infoText = [infoText stringByAppendingFormat:@" · %ld次浏览", (long)self.model.viewCount];
}
if (self.model.createTime.length > 0) {
NSString * timeStr = self.model.createTime;
if (timeStr.length >= 10) {
timeStr = [timeStr substringToIndex:10];
}
infoText = [infoText stringByAppendingFormat:@" · %@", timeStr];
}
self.infoLabel.text = infoText;
// 加载富文本
if (self.model.content.length > 0) {
NSString * htmlString = [self buildHTMLString:self.model.content];
[self.webView loadHTMLString:htmlString baseURL:nil];
} else {
[self updateWebViewHeight:0];
}
}
#pragma mark - ————— HTML 拼接 —————
- (NSString *)buildHTMLString:(NSString *)bodyContent {
NSString * html = [NSString stringWithFormat:@"<!DOCTYPE html>"
"<html><head>"
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0\">"
"<style>"
"body { font-family: -apple-system, sans-serif; font-size: 15px; color: #333; "
" line-height: 1.7; padding: 0; margin: 0; word-wrap: break-word; }"
"img { max-width: 100%%; height: auto; }"
"video { max-width: 100%%; height: auto; }"
"</style>"
"</head><body>%@</body></html>",
bodyContent];
return html;
}
#pragma mark - ————— WKNavigationDelegate —————
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
// 加载完成后获取内容实际高度
kWeakSelf(self);
[webView evaluateJavaScript:@"document.body.scrollHeight" completionHandler:^(id result, NSError * _Nullable error) {
CGFloat height = [result floatValue];
dispatch_async(dispatch_get_main_queue(), ^{
[weakself updateWebViewHeight:height];
});
}];
}
- (void)updateWebViewHeight:(CGFloat)height {
if (height <= 0) {
height = 1;
}
[self.webViewHeightConstraint uninstall];
[self.webView mas_updateConstraints:^(MASConstraintMaker *make) {
self.webViewHeightConstraint = make.height.mas_offset(height);
}];
}
#pragma mark - ————— URL 编码 —————
- (NSString *)encodeURL:(NSString *)url {
if (![url hasPrefix:@"http"]) {
url = [NSString stringWithFormat:@"%@%@", ResourceAddress, url];
}
url = [url stringByReplacingOccurrencesOfString:@"\\" withString:@"/"];
return [url stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]] ?: url;
}
#pragma mark - ————— Setter / Getter —————
@end
@@ -0,0 +1,17 @@
//
// XJSelectContactViewController.h
// XinjiangProject
//
#import "XJBaseViewController.h"
NS_ASSUME_NONNULL_BEGIN
@interface XJSelectContactViewController : XJBaseViewController
@property (nonatomic, copy) NSString *chatID;
@property (nonatomic, copy) NSString *groupId;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,385 @@
//
// XJSelectContactViewController.m
// XinjiangProject
//
#import "XJSelectContactViewController.h"
#import "XJPNetAPI.h"
typedef NS_ENUM(NSUInteger, XJContactTab) {
XJContactTabEmployee = 0,
XJContactTabExpert = 1,
};
@interface XJSelectContactViewController ()<UITableViewDelegate, UITableViewDataSource>
@property (nonatomic, assign) XJContactTab currentTab;
// 顶部切换
@property (nonatomic, strong) UIView *tabBgView;
@property (nonatomic, strong) UILabel *employeeTabLabel;
@property (nonatomic, strong) UILabel *expertTabLabel;
// 搜索
@property (nonatomic, strong) UITextField *searchField;
// 列表
@property (nonatomic, strong) UITableView *tableView;
@property (nonatomic, strong) NSMutableArray<NSDictionary *> *dataArray;
// 选中集合
@property (nonatomic, strong) NSMutableSet<NSString *> *selectedIds;
// 底部
@property (nonatomic, strong) UIView *bottomView;
@end
@implementation XJSelectContactViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.isHidenNaviBar = NO;
self.navigationItem.title = @"选择联系人";
self.view.backgroundColor = CViewBgColor;
self.currentTab = XJContactTabEmployee;
self.dataArray = [NSMutableArray array];
self.selectedIds = [NSMutableSet set];
[self createTopView];
[self createBottomView];
[self createTableView];
[self loadData];
}
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
CGFloat safeBottom = 0;
if (@available(iOS 11.0, *)) {
safeBottom = self.view.safeAreaInsets.bottom;
}
CGFloat bottomH = 60 + safeBottom;
self.bottomView.frame = CGRectMake(0, self.view.bounds.size.height - bottomH, kScreenWidth, bottomH);
CGFloat top = 94;
CGFloat tableBottom = self.bottomView.frame.origin.y - 15;
self.tableView.frame = CGRectMake(0, top, kScreenWidth, tableBottom - top);
}
#pragma mark - 顶部区域 (高度94)
- (void)createTopView {
UIView *container = [[UIView alloc] initWithFrame:CGRectMake(0, 0, kScreenWidth, 94)];
container.backgroundColor = KWhiteColor;
[self.view addSubview:container];
// 搜索按钮(右侧)
UIButton *searchBtn = [[UIButton alloc] initWithFrame:CGRectMake(kScreenWidth - 56, 6, 56, 32)];
[searchBtn setTitle:@"搜索" forState:UIControlStateNormal];
searchBtn.titleLabel.font = [UIFont systemFontOfSize:14 weight:UIFontWeightMedium];
[searchBtn setTitleColor:UIColorHex(0x2EB8B2) forState:UIControlStateNormal];
[searchBtn addTarget:self action:@selector(didTapSearch) forControlEvents:UIControlEventTouchUpInside];
[container addSubview:searchBtn];
// 搜索框
UIView *searchBg = [[UIView alloc] initWithFrame:CGRectMake(15, 6, kScreenWidth - 15 - 56, 32)];
searchBg.backgroundColor = UIColorHex(0xF6F6F6);
searchBg.layer.cornerRadius = 8;
searchBg.layer.masksToBounds = YES;
[container addSubview:searchBg];
UIImageView *searchIcon = [[UIImageView alloc] initWithFrame:CGRectMake(12, (32 - 14) / 2, 14, 14)];
searchIcon.image = [UIImage imageNamed:@"searchLeftIcon"];
searchIcon.contentMode = UIViewContentModeScaleAspectFit;
[searchBg addSubview:searchIcon];
self.searchField = [[UITextField alloc] initWithFrame:CGRectMake(34, 0, searchBg.bounds.size.width - 34, 32)];
self.searchField.font = [UIFont systemFontOfSize:14];
self.searchField.returnKeyType = UIReturnKeySearch;
self.searchField.placeholder = @"请输入姓名搜索";
[self.searchField addTarget:self action:@selector(didTapSearch) forControlEvents:UIControlEventEditingDidEndOnExit];
[searchBg addSubview:self.searchField];
// 切换标签(搜索框底部 + 10)
CGFloat tabY = 6 + 32 + 10;
self.tabBgView = [[UIView alloc] initWithFrame:CGRectMake(17, tabY, kScreenWidth - 34, 36)];
self.tabBgView.backgroundColor = UIColorHex(0xE8EBF2);
self.tabBgView.layer.cornerRadius = 8;
self.tabBgView.layer.masksToBounds = YES;
[container addSubview:self.tabBgView];
CGFloat tabW = (self.tabBgView.bounds.size.width - 4) / 2;
CGFloat tabH = 32;
UIView *employeeBg = [[UIView alloc] initWithFrame:CGRectMake(2, 2, tabW, tabH)];
employeeBg.backgroundColor = KWhiteColor;
employeeBg.layer.cornerRadius = 8;
employeeBg.layer.masksToBounds = YES;
employeeBg.tag = 200;
[self.tabBgView addSubview:employeeBg];
self.employeeTabLabel = [[UILabel alloc] initWithFrame:employeeBg.bounds];
self.employeeTabLabel.text = @"邀请员工";
self.employeeTabLabel.textAlignment = NSTextAlignmentCenter;
self.employeeTabLabel.font = [UIFont systemFontOfSize:14 weight:UIFontWeightMedium];
self.employeeTabLabel.textColor = UIColorHex(0x2EB8B2);
self.employeeTabLabel.userInteractionEnabled = YES;
[employeeBg addSubview:self.employeeTabLabel];
[employeeBg addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(didTapEmployeeTab)]];
UIView *expertBg = [[UIView alloc] initWithFrame:CGRectMake(2 + tabW, 2, tabW, tabH)];
expertBg.backgroundColor = UIColorHex(0xE8EBF2);
expertBg.layer.cornerRadius = 8;
expertBg.layer.masksToBounds = YES;
expertBg.tag = 201;
[self.tabBgView addSubview:expertBg];
self.expertTabLabel = [[UILabel alloc] initWithFrame:expertBg.bounds];
self.expertTabLabel.text = @"邀请专家";
self.expertTabLabel.textAlignment = NSTextAlignmentCenter;
self.expertTabLabel.font = [UIFont systemFontOfSize:14 weight:UIFontWeightRegular];
self.expertTabLabel.textColor = UIColorHex(0x808080);
self.expertTabLabel.userInteractionEnabled = YES;
[expertBg addSubview:self.expertTabLabel];
[expertBg addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(didTapExpertTab)]];
// 头部分割线
UIView *headerLine = [[UIView alloc] initWithFrame:CGRectMake(0, 93, kScreenWidth, 1)];
headerLine.backgroundColor = UIColorHex(0xEAECF1);
[container addSubview:headerLine];
}
#pragma mark - 底部区域
- (void)createBottomView {
self.bottomView = [[UIView alloc] initWithFrame:CGRectZero];
self.bottomView.backgroundColor = KWhiteColor;
[self.view addSubview:self.bottomView];
UIButton *confirmBtn = [[UIButton alloc] initWithFrame:CGRectMake(45, 8, kScreenWidth - 90, 44)];
confirmBtn.backgroundColor = UIColorHex(0x2EB8B1);
confirmBtn.layer.cornerRadius = 12;
confirmBtn.layer.masksToBounds = YES;
[confirmBtn setTitle:@"确定" forState:UIControlStateNormal];
[confirmBtn setTitleColor:UIColorHex(0xFFFFFF) forState:UIControlStateNormal];
confirmBtn.titleLabel.font = [UIFont boldSystemFontOfSize:18];
[confirmBtn addTarget:self action:@selector(didTapConfirm) forControlEvents:UIControlEventTouchUpInside];
[self.bottomView addSubview:confirmBtn];
}
#pragma mark - 列表
- (void)createTableView {
self.tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
self.tableView.delegate = self;
self.tableView.dataSource = self;
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
self.tableView.rowHeight = 70;
self.tableView.backgroundColor = CViewBgColor;
LYEmptyView *emptyView = [LYEmptyView emptyViewWithImageStr:@"empty_placeholder" titleStr:@"暂无数据" detailStr:@""];
emptyView.subViewMargin = 20.f;
emptyView.titleLabTextColor = CFontColor1;
emptyView.detailLabFont = [UIFont systemFontOfSize:17];
self.tableView.ly_emptyView = emptyView;
[self.view addSubview:self.tableView];
}
- (void)loadData {
[self showMBProgressHUDWithString:@"加载中..." autoHidden:NO];
[self.dataArray removeAllObjects];
[self.tableView reloadData];
NSMutableDictionary *params = [NSMutableDictionary dictionary];
if (self.groupId.length > 0) {
[params setObject:self.groupId forKey:@"excludeSessionId"];
}
NSString *keyword = self.searchField.text;
if (keyword.length > 0) {
[params setObject:keyword forKey:@"realname"];
}
[params setObject:@(1) forKey:@"pageNo"];
[params setObject:@(200) forKey:@"pageSize"];
NSString *tag = (self.currentTab == XJContactTabEmployee) ? @"SEARCH_EMPLOYEE" : @"SEARCH_EXPERT";
XJPNetAPI *api = [[XJPNetAPI alloc] init:self tag:tag NeedToken:@""];
if (self.currentTab == XJContactTabEmployee) {
[api getSearchEmployee:params];
} else {
[api getSearchExpert:params];
}
}
- (void)didTapSearch {
[self loadData];
}
- (void)didTapConfirm {
if (self.selectedIds.count == 0) {
[EasyTextView showErrorText:@"请先选择联系人"];
return;
}
[self showMBProgressHUDWithString:@"邀请中..." autoHidden:NO];
NSMutableDictionary *params = [NSMutableDictionary dictionary];
[params setObject:self.groupId forKey:@"groupId"];
[params setObject:[self.selectedIds allObjects] forKey:@"memberList"];
NSInteger memberType = (self.currentTab == XJContactTabEmployee) ? 1 : 0;
[params setObject:@(memberType) forKey:@"memberType"];
XJPNetAPI *api = [[XJPNetAPI alloc] init:self tag:@"ADDGROUPUSER" NeedToken:@""];
[api postAddGroupUser:params];
}
#pragma mark - 切换
- (void)didTapEmployeeTab {
if (self.currentTab == XJContactTabEmployee) return;
self.currentTab = XJContactTabEmployee;
[self updateTabStyle];
[self loadData];
}
- (void)didTapExpertTab {
if (self.currentTab == XJContactTabExpert) return;
self.currentTab = XJContactTabExpert;
[self updateTabStyle];
[self loadData];
}
- (void)updateTabStyle {
UIView *employeeBg = [self.tabBgView viewWithTag:200];
UIView *expertBg = [self.tabBgView viewWithTag:201];
if (self.currentTab == XJContactTabEmployee) {
employeeBg.backgroundColor = KWhiteColor;
self.employeeTabLabel.font = [UIFont systemFontOfSize:14 weight:UIFontWeightMedium];
self.employeeTabLabel.textColor = UIColorHex(0x2EB8B2);
expertBg.backgroundColor = UIColorHex(0xE8EBF2);
self.expertTabLabel.font = [UIFont systemFontOfSize:14 weight:UIFontWeightRegular];
self.expertTabLabel.textColor = UIColorHex(0x808080);
} else {
expertBg.backgroundColor = KWhiteColor;
self.expertTabLabel.font = [UIFont systemFontOfSize:14 weight:UIFontWeightMedium];
self.expertTabLabel.textColor = UIColorHex(0x2EB8B2);
employeeBg.backgroundColor = UIColorHex(0xE8EBF2);
self.employeeTabLabel.font = [UIFont systemFontOfSize:14 weight:UIFontWeightRegular];
self.employeeTabLabel.textColor = UIColorHex(0x808080);
}
}
#pragma mark - UITableView
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.dataArray.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellId = @"SelectContactCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellId];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.backgroundColor = KWhiteColor;
UIImageView *avatar = [[UIImageView alloc] initWithFrame:CGRectMake(15, 12, 45, 45)];
avatar.tag = 100;
avatar.layer.cornerRadius = 12;
avatar.layer.masksToBounds = YES;
[cell.contentView addSubview:avatar];
UILabel *nameLabel = [[UILabel alloc] initWithFrame:CGRectMake(72, 17, kScreenWidth - 72 - 51, 18)];
nameLabel.tag = 101;
nameLabel.font = [UIFont systemFontOfSize:15 weight:UIFontWeightMedium];
nameLabel.textColor = UIColorHex(0x000000);
[cell.contentView addSubview:nameLabel];
UILabel *deptLabel = [[UILabel alloc] initWithFrame:CGRectMake(72, 39, kScreenWidth - 72 - 51, 14)];
deptLabel.tag = 102;
deptLabel.font = [UIFont systemFontOfSize:12 weight:UIFontWeightRegular];
deptLabel.textColor = UIColorHex(0x808080);
[cell.contentView addSubview:deptLabel];
UIButton *checkBtn = [[UIButton alloc] initWithFrame:CGRectMake(kScreenWidth - 36, (70 - 21) / 2, 21, 21)];
checkBtn.tag = 103;
checkBtn.userInteractionEnabled = NO;
[cell.contentView addSubview:checkBtn];
UIView *line = [[UIView alloc] initWithFrame:CGRectMake(72, 70 - 1, kScreenWidth - 72, 1)];
line.tag = 104;
line.backgroundColor = UIColorHex(0xEAECF1);
[cell.contentView addSubview:line];
}
NSDictionary *contact = self.dataArray[indexPath.row];
UIImageView *avatar = [cell.contentView viewWithTag:100];
UILabel *nameLabel = [cell.contentView viewWithTag:101];
UILabel *deptLabel = [cell.contentView viewWithTag:102];
UIButton *checkBtn = [cell.contentView viewWithTag:103];
nameLabel.text = contact[@"realname"] ?: @"";
if (self.currentTab == XJContactTabEmployee) {
deptLabel.text = [NSString stringWithFormat:@"%@-%@", contact[@"orgName"] ?: @"", contact[@"deptName"] ?: @""];
} else {
deptLabel.text = contact[@"post_dictText"] ?: @"";
}
NSString *avatarPath = contact[@"avatar"];
if (avatarPath.length > 0) {
NSString *fullUrl = [avatarPath hasPrefix:@"http"] ? avatarPath : [NSString stringWithFormat:@"%@%@", ResourceAddress, avatarPath];
[avatar sd_setImageWithURL:[NSURL URLWithString:fullUrl] placeholderImage:[UIImage imageNamed:@"heard_man"]];
} else {
avatar.image = [UIImage imageNamed:@"heard_man"];
}
NSString *userId = contact[@"userId"] ?: @"";
BOOL selected = [self.selectedIds containsObject:userId];
NSString *iconName = selected ? @"firstAid_checkbox_selected" : @"firstAid_checkbox_unselected";
[checkBtn setImage:[UIImage imageNamed:iconName] forState:UIControlStateNormal];
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
NSDictionary *contact = self.dataArray[indexPath.row];
NSString *userId = contact[@"userId"] ?: @"";
if ([self.selectedIds containsObject:userId]) {
[self.selectedIds removeObject:userId];
} else {
[self.selectedIds addObject:userId];
}
[self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
}
#pragma mark - Network
- (void)Failed:(NSString *)message tag:(NSString *)tag {
[self hiddenAllMBProgressHUD];
[EasyTextView showErrorText:message];
}
- (void)Sucess:(id)response tag:(NSString *)tag {
[self hiddenAllMBProgressHUD];
if ([tag isEqualToString:@"SEARCH_EMPLOYEE"] || [tag isEqualToString:@"SEARCH_EXPERT"]) {
NSDictionary *dict = [response isKindOfClass:[NSDictionary class]] ? response : nil;
NSDictionary *result = dict[@"result"] ?: dict;
NSArray *records = result[@"records"];
if ([records isKindOfClass:[NSArray class]]) {
[self.dataArray addObjectsFromArray:records];
}
[self.tableView reloadData];
}
if ([tag isEqualToString:@"ADDGROUPUSER"]) {
[EasyTextView showSuccessText:@"邀请成功"];
[self.navigationController popViewControllerAnimated:YES];
}
}
@end
@@ -286,7 +286,8 @@
[self addWhiteToBlackTransparentGradientToView:self.grapView];
XMButton * callBtn = [[XMButton alloc] initWithFrame:CGRectMake(kScreenWidth/2 - 70 - kRealValue(33), grapView.height - 17 - 70 , 70, 70)];
// 120 按钮
XMButton * callBtn = [[XMButton alloc] initWithFrame:CGRectMake(kScreenWidth/4 - 35, grapView.height - 17 - 70, 70, 70)];
callBtn.style = SDButtonStyleTop;
callBtn.backgroundColor = KWhiteColor;
callBtn.layer.cornerRadius = 35.0f;
@@ -302,8 +303,9 @@
callBtn.imageX = 24;
callBtn.interval = 5;
[grapView addSubview:callBtn];
XMButton * sosBtn = [[XMButton alloc] initWithFrame:CGRectMake(kScreenWidth/2 + kRealValue(33), callBtn.top , 70,70)];
// 应急就医 按钮
XMButton * sosBtn = [[XMButton alloc] initWithFrame:CGRectMake(kScreenWidth/2 - 35, callBtn.top, 70, 70)];
sosBtn.style = SDButtonStyleTop;
sosBtn.backgroundColor = KWhiteColor;
sosBtn.layer.cornerRadius = 35.0f;
@@ -320,6 +322,24 @@
sosBtn.interval = 5;
[grapView addSubview:sosBtn];
// 急救员 按钮
XMButton * firstAiderBtn = [[XMButton alloc] initWithFrame:CGRectMake(kScreenWidth*3/4 - 35, callBtn.top, 70, 70)];
firstAiderBtn.style = SDButtonStyleTop;
firstAiderBtn.backgroundColor = KWhiteColor;
firstAiderBtn.layer.cornerRadius = 35.0f;
firstAiderBtn.layer.masksToBounds = true;
[firstAiderBtn setTitle:@"急救员" forState:UIControlStateNormal];
firstAiderBtn.titleLabel.font = SYSTEMFONT(11);
[firstAiderBtn setTitleColor:UIColorHex(#FF422F) forState:UIControlStateNormal];
[firstAiderBtn setImage:[UIImage imageNamed:@"sosPeople"] forState:UIControlStateNormal];
[firstAiderBtn addTarget:self action:@selector(callFirstAiderAction) forControlEvents:UIControlEventTouchUpInside];
firstAiderBtn.imageW = 22;
firstAiderBtn.imageH = 21;
firstAiderBtn.imageY = 19;
firstAiderBtn.imageX = 24;
firstAiderBtn.interval = 5;
[grapView addSubview:firstAiderBtn];
self.titles = @[@"油田医院",@"健康小屋",@"AED",@"救护车"];
self.titleView.segmentTitles = self.titles;
[self.titleView reloadData];
@@ -502,6 +522,22 @@
}];
}
#pragma mark: 急救员点击
- (void)callFirstAiderAction {
[self showMBProgressHUDWithString:@"急救发起中,请稍候..." autoHidden:false];
NSUserDefaults * user = [NSUserDefaults standardUserDefaults];
CGFloat longitude = [[user objectForKey:USERDEFAULLONG] floatValue];
CGFloat latitude = [[user objectForKey:USERDEFAULLATITUDE] floatValue];
NSMutableDictionary * parmDic = [[NSMutableDictionary alloc] init];
[parmDic setObject:@(longitude) forKey:@"longitude"];
[parmDic setObject:@(latitude) forKey:@"latitude"];
[parmDic setObject:@"急救员" forKey:@"name"];
XJPNetAPI * sosApi = [[XJPNetAPI alloc] init:self tag:@"FirstAidGroupAPI" NeedToken:@""];
[sosApi getCreateGroupMagFirstAider:parmDic];
}
#pragma mark:应急点击
- (void)callSoSAction {
@@ -683,12 +719,12 @@
[self hiddenAllMBProgressHUD];
if ([ESCTUIManager TUIUserStatus] == false) {
[self loginImAccount];
return;
}
SOSGroupIMModel * model = [SOSGroupIMModel modelWithDictionary:dic];
ChatViewController * chatVC = [[ChatViewController alloc] init];
@@ -703,7 +739,25 @@
chatVC.navTitle = @"应急中心";
chatVC.questionType = 1;
[self.navigationController pushViewController:chatVC animated:YES];
}
if ([tag isEqualToString:@"FirstAidGroupAPI"]) {
NSDictionary * dic = response;
[self hiddenAllMBProgressHUD];
if ([ESCTUIManager TUIUserStatus] == false) {
[self loginImAccount];
return;
}
SOSGroupIMModel * model = [SOSGroupIMModel modelWithDictionary:dic];
ChatViewController * chatVC = [[ChatViewController alloc] init];
chatVC.isGroupID = YES;
chatVC.isFromFirstAid = YES;
chatVC.isVideo = YES;
chatVC.chatID = model.groupId;
chatVC.navTitle = @"应急就医群";
[self.navigationController pushViewController:chatVC animated:YES];
}
if ([tag isEqualToString:@"loginIMAPI"]) {
@@ -0,0 +1,31 @@
//
// XJFirstAidResourceModel.h
// XinjiangProject
//
// 急救宣教资源 - 详情数据模型
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface XJFirstAidResourceModel : NSObject
@property (nonatomic, copy) NSString * resourceId;
@property (nonatomic, copy) NSString * title;
@property (nonatomic, copy) NSString * summary;
@property (nonatomic, copy) NSString * content; // 富文本 HTML
@property (nonatomic, copy) NSString * coverImage; // 封面图 URL
@property (nonatomic, copy) NSString * contentType; // IMAGE_TEXT/VIDEO/AUDIO/PDF
@property (nonatomic, copy) NSString * categoryName; // 分类名称
@property (nonatomic, copy) NSString * tags; // 标签ID(逗号分隔)
@property (nonatomic, strong) NSArray<NSString *> * tagNameList; // 标签名称列表
@property (nonatomic, copy) NSString * fileUrl; // 附件 JSON 数组字符串
@property (nonatomic, assign) NSInteger viewCount;
@property (nonatomic, assign) NSInteger favoriteCount;
@property (nonatomic, assign) BOOL isFavorited;
@property (nonatomic, copy) NSString * createTime;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,10 @@
//
// XJFirstAidResourceModel.m
// XinjiangProject
//
#import "XJFirstAidResourceModel.h"
@implementation XJFirstAidResourceModel
@end
@@ -63,6 +63,9 @@ NS_ASSUME_NONNULL_BEGIN
////是否评价过
@property (nonatomic, assign) BOOL isEveal;
// 是否从急救员入口进入
@property (nonatomic, assign) BOOL isFromFirstAid;
@end
NS_ASSUME_NONNULL_END
@@ -18,6 +18,9 @@
#import "XJExpertMainViewController.h"
#import "SendHealthDataViewController.h"
#import "CurrentLocationViewController.h"
#import "CustomMapMessageCell.h"
#import "TUIGroupMemberCellData.h"
#import "XJSelectContactViewController.h"
#import "TUITextMessageCellData.h"
#import "QuestionViewController.h"
#import "SendReportModel.h"
@@ -29,6 +32,10 @@
#import "PopupView.h"
#import "CustomTUIBaseMessageController.h"
#import "DoctorHomePageDetailVC.h"
#import "ArticleCardMessageCell.h"
#import "XJFirstAidResourceDetailVC.h"
#import "XJWeChatManager.h"
#import <SDWebImage/SDImageCache.h>
//自定义map
@@ -43,7 +50,7 @@
@class TUIChatDataProvider;
@interface ChatViewController ()<V2TIMSimpleMsgListener,TUIChatBaseDataProviderDelegate,IMCustomDeleagte,TUIMessageCellDelegate,TUIBaseMessageControllerDelegate,TUICallObserver,V2TIMConversationListener,CLLocationManagerDelegate,UIGestureRecognizerDelegate>
@interface ChatViewController ()<V2TIMSimpleMsgListener,TUIChatBaseDataProviderDelegate,IMCustomDeleagte,TUIMessageCellDelegate,TUIBaseMessageControllerDelegate,TUICallObserver,V2TIMConversationListener,CLLocationManagerDelegate,UIGestureRecognizerDelegate,UITableViewDelegate,UITableViewDataSource>
@property (nonatomic, strong) TUIC2CChatViewController *vc;
@property (nonatomic, strong) TUIGroupChatViewController * groupvc;
@@ -61,6 +68,13 @@
@property (nonatomic, copy) NSString * doctorCardID;
// 右侧群成员面板
@property (nonatomic, strong) UIView *memberPanel;
@property (nonatomic, strong) UIView *memberOverlay;
@property (nonatomic, strong) UITableView *memberTableView;
@property (nonatomic, strong) UILabel *memberTitleLabel;
@property (nonatomic, strong) NSMutableArray<NSDictionary *> *memberList;
@end
@implementation ChatViewController
@@ -94,6 +108,16 @@
[self addNavigationItemWithImageNames:@[@"whiteBack_icon"] isLeft:YES target:self action:@selector(backUp) tags:@[@200]];
// 右上角三个点(仅急救员入口显示)
if (self.isFromFirstAid) {
UIButton *moreBtn = [UIButton buttonWithType:UIButtonTypeSystem];
[moreBtn setTitle:@"···" forState:UIControlStateNormal];
moreBtn.titleLabel.font = [UIFont boldSystemFontOfSize:22];
[moreBtn setTitleColor:KWhiteColor forState:UIControlStateNormal];
[moreBtn addTarget:self action:@selector(moreAction) forControlEvents:UIControlEventTouchUpInside];
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:moreBtn];
}
TUIChatConversationModel *data = [[TUIChatConversationModel alloc] init];
HQUserInfo * user = [HQCommonUtils keyedUnarchiverWithKey:LoginUserInfo];
@@ -162,11 +186,12 @@
data.userID = self.chatID;
TUIC2CChatViewController *vc = [[TUIC2CChatViewController alloc] init];
self.vc = vc;
vc.messageController.delegate = self;
[vc setConversationData:data];
[self addChildViewController:vc];
self.view.backgroundColor = UIColorHex(#EEEEEE);
[self.view addSubview:vc.view];
// addSubview 触发 vc.viewDidLoad 后 messageController 才被创建,必须在这之后设 delegate
vc.messageController.delegate = self;
vc.needScrollToBottom = true;
vc.customDelegate = self;
}
@@ -175,14 +200,15 @@
data.groupID = self.chatID;
TUIGroupChatViewController * vc = [[TUIGroupChatViewController alloc] init];
self.groupvc = vc;
vc.messageController.delegate = self;
[vc setConversationData:data];
[self addChildViewController:vc];
self.view.backgroundColor = UIColorHex(#EEEEEE);
[self.view addSubview:vc.view];
// addSubview 触发 vc.viewDidLoad 后 messageController 才被创建,必须在这之后设 delegate
vc.messageController.delegate = self;
vc.needScrollToBottom = true;
vc.customDelegate = self;
}
// self.view.backgroundColor = UIColorHex(#EEEEEE);
if ([self.tfNew integerValue] == 1) {
@@ -277,10 +303,262 @@
-(void)backUp {
[self.navigationController popViewControllerAnimated:YES];
}
#pragma mark: 右上角更多(群成员侧滑面板)
- (void)moreAction {
if (!self.isGroupID) return;
if (self.memberPanel) { [self hideMemberPanel]; return; }
UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow;
CGFloat panelW = kScreenWidth - 94;
// 半透明遮罩
self.memberOverlay = [[UIView alloc] initWithFrame:keyWindow.bounds];
self.memberOverlay.backgroundColor = RGBA(0, 0, 0, 0.4);
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(hideMemberPanel)];
[self.memberOverlay addGestureRecognizer:tap];
[keyWindow addSubview:self.memberOverlay];
// 右侧面板
self.memberPanel = [[UIView alloc] initWithFrame:CGRectMake(kScreenWidth, 0, panelW, kScreenHeight)];
self.memberPanel.backgroundColor = KWhiteColor;
[keyWindow addSubview:self.memberPanel];
// 头部区域
UIView *headerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, panelW, kStatusBarHeight + 44)];
headerView.backgroundColor = KWhiteColor;
[self.memberPanel addSubview:headerView];
self.memberTitleLabel = [[UILabel alloc] initWithFrame:CGRectMake(12, kStatusBarHeight, panelW - 56, 44)];
self.memberTitleLabel.text = @"群成员";
self.memberTitleLabel.textAlignment = NSTextAlignmentLeft;
self.memberTitleLabel.font = [UIFont systemFontOfSize:15 weight:UIFontWeightMedium];
self.memberTitleLabel.textColor = UIColorHex(0x070809);
[headerView addSubview:self.memberTitleLabel];
// 关闭按钮
UIButton *closeBtn = [[UIButton alloc] initWithFrame:CGRectMake(panelW - 44, kStatusBarHeight, 44, 44)];
[closeBtn setTitle:@"" forState:UIControlStateNormal];
[closeBtn setTitleColor:UIColorHex(0x070809) forState:UIControlStateNormal];
[closeBtn addTarget:self action:@selector(hideMemberPanel) forControlEvents:UIControlEventTouchUpInside];
[headerView addSubview:closeBtn];
// 头部分割线
UIView *headerLine = [[UIView alloc] initWithFrame:CGRectMake(12, headerView.bounds.size.height - 1, panelW - 12, 1)];
headerLine.backgroundColor = UIColorHex(0xEAECF1);
[headerView addSubview:headerLine];
// 成员列表
CGFloat tableY = kStatusBarHeight + 44;
self.memberTableView = [[UITableView alloc] initWithFrame:CGRectMake(0, tableY, panelW, kScreenHeight - tableY) style:UITableViewStylePlain];
self.memberTableView.delegate = self;
self.memberTableView.dataSource = self;
self.memberTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
self.memberTableView.rowHeight = 68;
self.memberTableView.tableFooterView = [self makeAddMemberFooterView:panelW];
[self.memberPanel addSubview:self.memberTableView];
// 动画滑入
[UIView animateWithDuration:0.25 animations:^{
self.memberPanel.frame = CGRectMake(kScreenWidth - panelW, 0, panelW, kScreenHeight);
}];
[self loadGroupMembers];
}
- (UIView *)makeAddMemberFooterView:(CGFloat)panelW {
UIView *footer = [[UIView alloc] initWithFrame:CGRectMake(0, 0, panelW, 20 + 49)];
footer.backgroundColor = KWhiteColor;
UIButton *addBtn = [[UIButton alloc] initWithFrame:CGRectMake(11, 20, panelW - 22, 49)];
addBtn.backgroundColor = [UIColorHex(0x2EB8B2) colorWithAlphaComponent:0.12];
addBtn.layer.cornerRadius = 8;
addBtn.layer.borderWidth = 1;
addBtn.layer.borderColor = UIColorHex(0x2EB8B2).CGColor;
[addBtn addTarget:self action:@selector(addMemberAction) forControlEvents:UIControlEventTouchUpInside];
UIImageView *icon = [[UIImageView alloc] initWithFrame:CGRectMake(0, (49 - 18) / 2, 18, 18)];
icon.image = [UIImage imageNamed:@"firstAid_add_icon"];
icon.contentMode = UIViewContentModeScaleAspectFit;
[addBtn addSubview:icon];
UILabel *btnTitle = [[UILabel alloc] init];
btnTitle.text = @"添加成员";
btnTitle.textColor = UIColorHex(0x2EB8B2);
btnTitle.font = [UIFont systemFontOfSize:16 weight:UIFontWeightMedium];
[btnTitle sizeToFit];
CGFloat totalW = 18 + 6 + btnTitle.bounds.size.width;
icon.frame = CGRectMake((addBtn.bounds.size.width - totalW) / 2, (49 - 18) / 2, 18, 18);
btnTitle.frame = CGRectMake(icon.frame.origin.x + 18 + 6, (49 - btnTitle.bounds.size.height) / 2, btnTitle.bounds.size.width, btnTitle.bounds.size.height);
[addBtn addSubview:btnTitle];
[footer addSubview:addBtn];
return footer;
}
- (void)hideMemberPanel {
CGFloat panelW = kScreenWidth - 94;
[UIView animateWithDuration:0.25 animations:^{
self.memberPanel.frame = CGRectMake(kScreenWidth, 0, panelW, kScreenHeight);
} completion:^(BOOL finished) {
[self.memberOverlay removeFromSuperview];
[self.memberPanel removeFromSuperview];
self.memberOverlay = nil;
self.memberPanel = nil;
self.memberTableView = nil;
self.memberList = nil;
}];
}
- (void)loadGroupMembers {
self.memberList = [NSMutableArray array];
NSMutableDictionary *params = [NSMutableDictionary dictionary];
[params setObject:self.chatID forKey:@"sessionId"];
XJPNetAPI *api = [[XJPNetAPI alloc] init:self tag:@"GROUPMEMBERLIST" NeedToken:@""];
[api getActualGroupMemberList:params];
}
/// memberType 转中文
- (NSString *)memberTypeNameFromValue:(NSInteger)memberType {
switch (memberType) {
case -1: return @"发起人";
case 0: return @"操作人员";
case 1: return @"专业人员";
case 2: return @"员工";
default: return @"";
}
}
- (void)addMemberAction {
[self hideMemberPanel];
XJSelectContactViewController *selectVC = [[XJSelectContactViewController alloc] init];
selectVC.chatID = self.chatID;
selectVC.groupId = self.chatID;
[self.navigationController pushViewController:selectVC animated:YES];
}
#pragma mark - 群成员列表
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (tableView != self.memberTableView || !self.memberList) return 0;
return self.memberList.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if (tableView != self.memberTableView || indexPath.row >= self.memberList.count) return [UITableViewCell new];
static NSString *cellId = @"MemberCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellId];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
CGFloat panelW = kScreenWidth - 94;
// 头像
UIImageView *avatar = [[UIImageView alloc] initWithFrame:CGRectMake(12, 11, 45, 45)];
avatar.tag = 100;
avatar.layer.cornerRadius = 8;
avatar.layer.masksToBounds = YES;
[cell.contentView addSubview:avatar];
// 姓名
UILabel *nameLabel = [[UILabel alloc] init];
nameLabel.tag = 101;
nameLabel.font = [UIFont systemFontOfSize:15 weight:UIFontWeightMedium];
nameLabel.textColor = UIColorHex(0x070809);
[cell.contentView addSubview:nameLabel];
// 本人标识
UILabel *selfBadge = [[UILabel alloc] init];
selfBadge.tag = 105;
selfBadge.text = @"本人";
selfBadge.font = [UIFont systemFontOfSize:10 weight:UIFontWeightSemibold];
selfBadge.textColor = UIColorHex(0x2EB8B2);
selfBadge.backgroundColor = [UIColorHex(0x2EB8B2) colorWithAlphaComponent:0.12];
selfBadge.textAlignment = NSTextAlignmentCenter;
selfBadge.layer.cornerRadius = 4;
selfBadge.layer.masksToBounds = YES;
selfBadge.hidden = YES;
[cell.contentView addSubview:selfBadge];
// 成员类型
UILabel *titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(70, 42, panelW - 132, 14)];
titleLabel.tag = 102;
titleLabel.font = [UIFont systemFontOfSize:12 weight:UIFontWeightRegular];
titleLabel.textColor = UIColorHex(0x808080);
[cell.contentView addSubview:titleLabel];
// 电话按钮
UIButton *phoneBtn = [[UIButton alloc] initWithFrame:CGRectMake(panelW - 47, 20, 32, 32)];
phoneBtn.tag = 103;
[phoneBtn setImage:[UIImage imageNamed:@"firstAid_phone_icon"] forState:UIControlStateNormal];
[phoneBtn addTarget:self action:@selector(callMemberAction:) forControlEvents:UIControlEventTouchUpInside];
[cell.contentView addSubview:phoneBtn];
// 分割线
UIView *line = [[UIView alloc] initWithFrame:CGRectMake(70, 67, panelW - 70, 1)];
line.tag = 104;
line.backgroundColor = UIColorHex(0xEAECF1);
[cell.contentView addSubview:line];
}
NSDictionary *member = self.memberList[indexPath.row];
UIImageView *avatar = [cell.contentView viewWithTag:100];
UILabel *nameLabel = [cell.contentView viewWithTag:101];
UILabel *titleLabel = [cell.contentView viewWithTag:102];
UILabel *selfBadge = [cell.contentView viewWithTag:105];
CGFloat panelW = kScreenWidth - 94;
// 姓名
NSString *userName = member[@"userName"] ?: @"";
nameLabel.text = userName;
CGSize nameSize = [userName sizeWithAttributes:@{NSFontAttributeName: nameLabel.font}];
nameLabel.frame = CGRectMake(70, 15, nameSize.width, 17);
// 本人标识
BOOL isSelfUser = [member[@"isSelf"] integerValue] == 1;
if (isSelfUser) {
selfBadge.hidden = NO;
selfBadge.frame = CGRectMake(CGRectGetMaxX(nameLabel.frame) + 6, 16, 30, 16);
} else {
selfBadge.hidden = YES;
}
// 成员类型
titleLabel.text = [self memberTypeNameFromValue:[member[@"memberType"] integerValue]];
NSString *avatarPath = member[@"avatar"];
if (avatarPath.length > 0) {
NSString *fullUrl = [avatarPath hasPrefix:@"http"] ? avatarPath : [NSString stringWithFormat:@"%@%@", ResourceAddress, avatarPath];
[avatar sd_setImageWithURL:[NSURL URLWithString:fullUrl] placeholderImage:[UIImage imageNamed:@"heard_man"]];
} else {
avatar.image = [UIImage imageNamed:@"heard_man"];
}
return cell;
}
- (void)callMemberAction:(UIButton *)sender {
UITableViewCell *cell = (UITableViewCell *)sender.superview.superview;
NSIndexPath *indexPath = [self.memberTableView indexPathForCell:cell];
if (!indexPath || indexPath.row >= self.memberList.count) return;
NSDictionary *member = self.memberList[indexPath.row];
NSString *phone = member[@"phone"];
if (phone.length == 0) {
[EasyTextView showErrorText:@"暂无手机号"];
return;
}
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"tel://%@", phone]] options:nil completionHandler:nil];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
#pragma mark:跳转到地图选择页
- (void)jumpChatMapView {
@@ -494,6 +772,15 @@
experVC.danganID = healthCell.fileId;
[self.navigationController pushViewController:experVC animated:YES];
}
if ([cell isKindOfClass:ArticleCardMessageCell.class]) {
ArticleCardMessageCell * articleCell = (ArticleCardMessageCell *)cell;
if (articleCell.resourceId.length > 0) {
XJFirstAidResourceDetailVC * detailVC = [[XJFirstAidResourceDetailVC alloc] init];
detailVC.resourceId = articleCell.resourceId;
[self.navigationController pushViewController:detailVC animated:YES];
}
}
}
- (void)getDocotorInfo:(long)sendTime{
@@ -599,17 +886,26 @@
}
if ([tag isEqualToString:@"smallAPI"]) {
bool isSmallHelp = [response boolValue];
if (isSmallHelp == true) {
DoctorHomePageDetailVC * vc = [[DoctorHomePageDetailVC alloc] init];
vc.userID = self.userID;
[self.navigationController pushViewController:vc animated:YES];
}
}
if ([tag isEqualToString:@"GROUPMEMBERLIST"]) {
[self.memberList removeAllObjects];
NSArray *list = [response isKindOfClass:[NSDictionary class]] ? response[@"result"] : response;
if ([list isKindOfClass:[NSArray class]]) {
[self.memberList addObjectsFromArray:list];
}
self.memberTitleLabel.text = [NSString stringWithFormat:@"群成员(%lu)", (unsigned long)self.memberList.count];
[self.memberTableView reloadData];
}
}
@@ -938,8 +1234,8 @@
// 处理授权成功的情况(例如,开始更新位置信息)
ChatMapViewController * mapVC = [[ChatMapViewController alloc] init];
MJWeakSelf
mapVC.mapBlcok = ^(CLLocationCoordinate2D coordinate) {
V2TIMMessage * message = [[V2TIMManager sharedInstance] createLocationMessage:@"位置" longitude:coordinate.longitude latitude:coordinate.latitude];
mapVC.mapBlcok = ^(CLLocationCoordinate2D coordinate, NSString *address) {
V2TIMMessage * message = [[V2TIMManager sharedInstance] createLocationMessage:address ?: @"位置" longitude:coordinate.longitude latitude:coordinate.latitude];
if( self.isGroupID == false) {
[weakSelf.vc sendMessage:message];
}else
@@ -983,4 +1279,93 @@
//}
#pragma mark - ————— 地图卡片长按转发微信 —————
- (void)messageController:(TUIBaseMessageController *)controller willDisplayCell:(TUIMessageCell *)cell withData:(TUIMessageCellData *)data {
// 运行时判断,避免直接 import Pods 中未被暴露的头文件
Class mapCellClass = NSClassFromString(@"MapMessageCustomCell");
BOOL isMapCell = [cell isKindOfClass:mapCellClass];
NSLog(@"[ChatVC] willDisplayCell: cell=%@, isMapCell=%d, class=%@", cell, isMapCell, NSStringFromClass([cell class]));
if (isMapCell) {
if ([cell respondsToSelector:@selector(setMapDelegate:)]) {
[cell performSelector:@selector(setMapDelegate:) withObject:self];
NSLog(@"[ChatVC] mapDelegate set to self on %@", cell);
}
}
}
- (void)mapCellDidLongPressWithData:(id)data {
NSLog(@"[ChatVC] mapCellDidLongPress called, data=%@", data);
if (![[XJWeChatManager sharedManager] isWeChatInstalled]) {
[EasyTextView showErrorText:@"未安装微信"];
return;
}
UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet];
[alert addAction:[UIAlertAction actionWithTitle:@"转发到微信" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
[self shareMapToWeChatWithData:data];
}]];
[alert addAction:[UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:nil]];
UIViewController *topVC = [self topViewController];
[topVC presentViewController:alert animated:YES completion:nil];
}
- (void)shareMapToWeChatWithData:(id)data {
NSString *address = [data valueForKey:@"text"];
if (![address isKindOfClass:[NSString class]] || address.length == 0) {
address = @"我的位置";
}
CGFloat lat = [[data valueForKey:@"latitude"] doubleValue];
CGFloat lng = [[data valueForKey:@"longitude"] doubleValue];
NSString *encodedAddress = [address stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
NSString *mapUrl = [NSString stringWithFormat:@"https://uri.amap.com/marker?position=%f,%f&name=%@",
lng, lat, encodedAddress ?: @"位置"];
NSString *imageKey = [data valueForKey:@"MapPictureImg"];
UIImage *thumb = [[SDImageCache sharedImageCache] imageFromCacheForKey:imageKey];
[[XJWeChatManager sharedManager] shareWebpageWithTitle:@"我的位置"
desc:address
webpageUrl:mapUrl
thumbImage:thumb
toScene:0
completion:^(BOOL success, NSString * _Nullable errorMsg) {
if (!success && errorMsg.length > 0) {
[EasyTextView showErrorText:errorMsg];
}
}];
}
- (UIViewController *)topViewController {
UIViewController *rootVC = [UIApplication sharedApplication].keyWindow.rootViewController;
while (rootVC.presentedViewController) {
rootVC = rootVC.presentedViewController;
}
if ([rootVC isKindOfClass:[UINavigationController class]]) {
return [(UINavigationController *)rootVC topViewController];
}
if ([rootVC isKindOfClass:[UITabBarController class]]) {
UIViewController *selected = [(UITabBarController *)rootVC selectedViewController];
if ([selected isKindOfClass:[UINavigationController class]]) {
return [(UINavigationController *)selected topViewController];
}
return selected;
}
return rootVC;
}
/// 将未实现的 TUIBaseMessageControllerDelegate 方法转发给 TUIBaseChatViewControllerself.vc / self.groupvc
- (id)forwardingTargetForSelector:(SEL)aSelector {
// 优先转发给 C2C 的 vc,其次 groupvc
if ([self.vc respondsToSelector:aSelector]) {
return self.vc;
}
if ([self.groupvc respondsToSelector:aSelector]) {
return self.groupvc;
}
return [super forwardingTargetForSelector:aSelector];
}
@end
@@ -9,7 +9,7 @@
NS_ASSUME_NONNULL_BEGIN
typedef void(^sendUserSoSLocation)(CLLocationCoordinate2D coordinate);
typedef void(^sendUserSoSLocation)(CLLocationCoordinate2D coordinate, NSString *address);
@interface ChatMapViewController : XJBaseViewController
@@ -14,6 +14,8 @@
@property (nonatomic, strong) MAMapView *mapView;
@property (nonatomic, strong) AMapSearchAPI *search;
@property (nonatomic, assign) BOOL userFirstLoca;
@property (nonatomic, copy) NSString *selectedAddress;
@property (nonatomic, strong) UILabel *addressLabel;
@end
@implementation ChatMapViewController
@@ -50,22 +52,46 @@
}
- (void)createUI {
self.mapView = [[MAMapView alloc] initWithFrame:self.view.bounds];
self.mapView.zoomLevel = 17;
self.mapView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
self.mapView.userTrackingMode = MAUserTrackingModeFollow;
[self.view addSubview:self.mapView];
self.search = [[AMapSearchAPI alloc] init];
self.search.delegate = self;
self.mapView.delegate = self;
[self addNavigationItemWithTitles:@[@"发送"] isLeft:NO target:self action:@selector(sendMapmessate) tags:@[@"100"]];
// 地址标签容器(大头针上方,灰色半透明圆角)
UIView *addressBgView = [[UIView alloc] init];
addressBgView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.55];
addressBgView.layer.cornerRadius = 8;
addressBgView.layer.masksToBounds = YES;
[self.view addSubview:addressBgView];
[self.view bringSubviewToFront:addressBgView];
[addressBgView mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.equalTo(self.view);
make.bottom.equalTo(self.view.mas_centerY).offset(-35);
make.width.lessThanOrEqualTo(@(kScreenWidth - 60));
}];
self.addressLabel = [[UILabel alloc] init];
self.addressLabel.text = @"定位中...";
self.addressLabel.numberOfLines = 2;
self.addressLabel.textAlignment = NSTextAlignmentCenter;
self.addressLabel.font = SYSTEMFONT(14);
self.addressLabel.textColor = KWhiteColor;
[addressBgView addSubview:self.addressLabel];
[self.addressLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(addressBgView).insets(UIEdgeInsetsMake(8, 12, 8, 12));
}];
}
- (void)sendMapmessate {
if (self.mapBlcok) {
self.mapBlcok(coordinate);
self.mapBlcok(coordinate, self.selectedAddress);
}
[self.navigationController popViewControllerAnimated:YES];
@@ -101,7 +127,6 @@
// 目的是只用第一次定位的数据,即用户的初始位置
if (self.userFirstLoca == NO) {
self.userFirstLoca = YES;
// self.userFirstCoor = userLocation.location.coordinate;
coordinate = userLocation.location.coordinate;
MAPointAnnotation *ann = [[MAPointAnnotation alloc] init];
ann.coordinate = userLocation.location.coordinate;
@@ -109,6 +134,12 @@
ann.lockedToScreen = YES;
ann.lockedScreenPoint = CGPointMake(mapView.center.x, mapView.center.y);
[mapView addAnnotation:ann];
// 首次定位后立即反查地址
AMapReGeocodeSearchRequest *rego = [[AMapReGeocodeSearchRequest alloc] init];
rego.requireExtension = YES;
rego.location = [AMapGeoPoint locationWithLatitude:coordinate.latitude longitude:coordinate.longitude];
[self.search AMapReGoecodeSearch:rego];
}
}
@@ -116,11 +147,11 @@
- (void)mapView:(MAMapView *)mapView mapDidMoveByUser:(BOOL)wasUserAction
{
// 将指定view坐标系的坐标转换为经纬度
CLLocationCoordinate2D moveCoor = [mapView convertPoint:CGPointMake(mapView.center.x, mapView.center.y-64) toCoordinateFromView:mapView];
CLLocationCoordinate2D moveCoor = [mapView convertPoint:CGPointMake(mapView.center.x, mapView.center.y) toCoordinateFromView:mapView];
coordinate = moveCoor;
// 通过坐标点得到经纬度,从而进行反地理编码
AMapReGeocodeSearchRequest *rego = [[AMapReGeocodeSearchRequest alloc] init];
// 是否返回扩展信(因为我们需要得到附近地理位置信息,所以设为YES)
rego.requireExtension = YES;
rego.location = [AMapGeoPoint locationWithLatitude:moveCoor.latitude longitude:moveCoor.longitude];
[self.search AMapReGoecodeSearch:rego];
@@ -129,7 +160,8 @@
/* 逆地理编码查询回调函数 */
- (void)onReGeocodeSearchDone:(AMapReGeocodeSearchRequest *)request response:(AMapReGeocodeSearchResponse *)response
{
// reGeodeVC就是一个简单的tableView布局,数据来源就是response.regeocode.pois
self.selectedAddress = response.regeocode.formattedAddress;
self.addressLabel.text = self.selectedAddress;
}
/* 这个是reGeodeVC点击tableViewCell的回调 */
@@ -0,0 +1,21 @@
//
// ArticleCardCellData.h
// XinjiangProject
//
// 急救宣教推送 - 自定义消息数据模型
//
#import "TUIMessageCellData.h"
NS_ASSUME_NONNULL_BEGIN
@interface ArticleCardCellData : TUIMessageCellData
@property (nonatomic, copy) NSString *imageUrl; // 封面图 URL(可为空)
@property (nonatomic, copy) NSString *title; // 标题
@property (nonatomic, copy) NSString *content; // 内容摘要
@property (nonatomic, copy) NSString *resourceId; // 宣教资源ID
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,63 @@
//
// ArticleCardCellData.m
// XinjiangProject
//
#import "ArticleCardCellData.h"
@implementation ArticleCardCellData
+ (TUIMessageCellData *)getCellData:(V2TIMMessage *)message {
NSDictionary *param = [NSJSONSerialization JSONObjectWithData:message.customElem.data
options:NSJSONReadingAllowFragments
error:nil];
ArticleCardCellData *cellData = [[ArticleCardCellData alloc] initWithDirection:message.isSelf ? MsgDirectionOutgoing : MsgDirectionIncoming];
cellData.innerMessage = message;
cellData.msgID = message.msgID;
cellData.avatarUrl = [NSURL URLWithString:message.faceURL];
cellData.reuseId = @"ArticleCardMessageCell";
// 过滤 nil 值,避免 OC 把 nil 转成 "(null)" 字符串
id imageUrlObj = param[@"messageImageUrl"];
id titleObj = param[@"title"];
id contentObj = param[@"content"];
id resourceIdObj = param[@"resourceId"];
cellData.imageUrl = ([imageUrlObj isKindOfClass:[NSString class]] && [imageUrlObj length] > 0) ? imageUrlObj : nil;
cellData.title = ([titleObj isKindOfClass:[NSString class]]) ? titleObj : @"";
cellData.content = ([contentObj isKindOfClass:[NSString class]]) ? contentObj : @"";
cellData.resourceId = ([resourceIdObj isKindOfClass:[NSString class]]) ? resourceIdObj : @"";
return cellData;
}
- (CGSize)contentSize {
CGFloat cardWidth = 260.f;
CGFloat textAreaWidth;
if (self.imageUrl.length > 0) {
textAreaWidth = cardWidth - 16 - 50 - 12 - 16; // 左边距 + 图片宽 + 间距 + 右边距
} else {
textAreaWidth = cardWidth - 16 - 16; // 左右边距各16
}
// 标题单行高度
CGFloat titleHeight = 22.f;
// 内容最多2行
CGFloat contentMaxHeight = 40.f;
CGRect contentRect = [self.content boundingRectWithSize:CGSizeMake(textAreaWidth, contentMaxHeight)
options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
attributes:@{NSFontAttributeName : [UIFont systemFontOfSize:13]}
context:nil];
CGFloat contentHeight = MIN(ceilf(contentRect.size.height), contentMaxHeight);
CGFloat textTotalHeight = titleHeight + 6 + contentHeight;
CGFloat imageHeight = (self.imageUrl.length > 0) ? 50.f : 0.f;
CGFloat totalHeight = 14 + MAX(textTotalHeight, imageHeight) + 14;
return CGSizeMake(cardWidth, totalHeight);
}
@end
@@ -0,0 +1,32 @@
//
// ArticleCardMessageCell.h
// XinjiangProject
//
// 急救宣教推送 - 自定义消息气泡
// ┌─────────────────────────────────┐
// │ 白色圆角卡片 │
// │ ┌────────┬────────────────────┐ │
// │ │ 封面图 │ 标题(15pt #1A1A1A)│ │
// │ │ 80×60 │ 内容(13pt #888 │ │
// │ └────────┴────────────────────┘ │
// │ (无封面时标题和内容直接左边距) │
// └─────────────────────────────────┘
//
#import "TUIMessageCell.h"
#import "ArticleCardCellData.h"
NS_ASSUME_NONNULL_BEGIN
@interface ArticleCardMessageCell : TUIMessageCell
@property (nonatomic, strong) UIImageView *coverImageView; // 封面图
@property (nonatomic, strong) UILabel *titleLabel; // 标题
@property (nonatomic, strong) UILabel *contentLabel; // 内容摘要
@property (nonatomic, copy) NSString *resourceId; // 宣教资源ID
- (void)fillWithData:(ArticleCardCellData *)data;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,123 @@
//
// ArticleCardMessageCell.m
// XinjiangProject
//
#import "ArticleCardMessageCell.h"
// 布局常量
static CGFloat const kCardPaddingH = 16.f; // 卡片水平内边距
static CGFloat const kCardPaddingV = 14.f; // 卡片垂直内边距
static CGFloat const kCoverImageWidth = 50.f; // 封面图宽度(对齐医生卡片头像)
static CGFloat const kCoverImageHeight = 50.f; // 封面图高度
static CGFloat const kCoverImageRadius = 6.f; // 封面图圆角
static CGFloat const kCoverTextGap = 12.f; // 封面图与文字间距
static CGFloat const kTitleContentGap = 6.f; // 标题与内容间距
@implementation ArticleCardMessageCell
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
[self createUI];
}
return self;
}
- (void)createUI {
self.container.layer.cornerRadius = 10;
self.container.backgroundColor = KWhiteColor;
// 封面图
self.coverImageView = [[UIImageView alloc] init];
self.coverImageView.layer.masksToBounds = YES;
self.coverImageView.layer.cornerRadius = kCoverImageRadius;
self.coverImageView.contentMode = UIViewContentModeScaleAspectFill;
self.coverImageView.hidden = YES;
[self.container addSubview:self.coverImageView];
// 标题
self.titleLabel = [[UILabel alloc] init];
self.titleLabel.font = [UIFont systemFontOfSize:15 weight:UIFontWeightMedium];
self.titleLabel.textColor = UIColorHex(0x1A1A1A);
self.titleLabel.textAlignment = NSTextAlignmentLeft;
self.titleLabel.numberOfLines = 1;
[self.container addSubview:self.titleLabel];
// 内容摘要
self.contentLabel = [[UILabel alloc] init];
self.contentLabel.font = [UIFont systemFontOfSize:13 weight:UIFontWeightRegular];
self.contentLabel.textColor = UIColorHex(0x888888);
self.contentLabel.textAlignment = NSTextAlignmentLeft;
self.contentLabel.numberOfLines = 2;
[self.container addSubview:self.contentLabel];
}
- (void)fillWithData:(ArticleCardCellData *)data {
[super fillWithData:data];
self.titleLabel.text = data.title;
self.contentLabel.text = data.content;
self.resourceId = data.resourceId;
BOOL hasImage = (data.imageUrl.length > 0);
self.coverImageView.hidden = !hasImage;
if (hasImage) {
NSString *encodedUrl = [self encodeImageURL:data.imageUrl];
[self.coverImageView sd_setImageWithURL:[NSURL URLWithString:encodedUrl]
placeholderImage:nil];
}
}
- (void)layoutSubviews {
[super layoutSubviews];
CGFloat containerW = self.container.bounds.size.width;
BOOL hasImage = !self.coverImageView.hidden;
CGFloat textOriginX;
CGFloat textMaxW;
if (hasImage) {
// 封面图:左侧
self.coverImageView.frame = CGRectMake(kCardPaddingH,
kCardPaddingV,
kCoverImageWidth,
kCoverImageHeight);
textOriginX = CGRectGetMaxX(self.coverImageView.frame) + kCoverTextGap;
textMaxW = containerW - textOriginX - kCardPaddingH;
} else {
textOriginX = kCardPaddingH;
textMaxW = containerW - kCardPaddingH - kCardPaddingH;
}
// 标题
self.titleLabel.frame = CGRectMake(textOriginX,
kCardPaddingV,
textMaxW,
22);
// 内容(标题下方)
CGFloat contentY = CGRectGetMaxY(self.titleLabel.frame) + kTitleContentGap;
CGFloat contentHeight = self.container.bounds.size.height - kCardPaddingV - contentY;
self.contentLabel.frame = CGRectMake(textOriginX,
contentY,
textMaxW,
contentHeight);
}
#pragma mark - URL 编码
- (NSString *)encodeImageURL:(NSString *)url {
if (![url containsString:@"https"] && ![url containsString:@"http"]) {
url = [NSString stringWithFormat:@"%@%@", ResourceAddress, url];
}
NSString *str1 = @"\\";
url = [url stringByReplacingOccurrencesOfString:str1 withString:@"/"];
return CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault,
(CFStringRef)url,
(CFStringRef)@"!$&'()*+,-./:;=?@_~%#[]",
NULL,
kCFStringEncodingUTF8));
}
@end
@@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "firstAid_phone_icon.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

@@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "firstAid_checkbox_selected.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "firstAid_checkbox_unselected.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "firstAid_phone_icon.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

@@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "health_icon_bp.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

@@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "health_icon_glu.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

@@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "health_icon_lipid.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

@@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "health_icon_ua.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

@@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "health_medical_deco.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

@@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "微信图片_20260707155356_208_97.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "opinion_line_right.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

@@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "opinion_line_left.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

@@ -1,11 +1,11 @@
{
"images" : [
{
"filename" : "navBackImg.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "navBackImg.png",
"idiom" : "universal",
"scale" : "2x"
},
@@ -18,6 +18,14 @@
<key>CFBundleTypeRole</key>
<string>Editor</string>
</dict>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLSchemes</key>
<array>
<string>wxb33cd60beda0a212</string>
</array>
</dict>
</array>
<key>LSApplicationQueriesSchemes</key>
<array>
@@ -25,6 +33,8 @@
<string>comgooglemaps</string>
<string>baidumap</string>
<string>iosamap</string>
<string>weixin</string>
<string>weixinULAPI</string>
</array>
<key>Localization native development region</key>
<string>China</string>
@@ -40,5 +50,7 @@
<key>UISceneConfigurations</key>
<dict/>
</dict>
<key>UIDesignRequiresCompatibility</key>
<true/>
</dict>
</plist>
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:www.rk-health.com</string>
</array>
</dict>
</plist>