KSNewWhiteBoard.m 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739
  1. //
  2. // KSNewWhiteBoard.m
  3. // TeacherDaya
  4. //
  5. // Created by 王智 on 2022/8/8.
  6. // Copyright © 2022 DayaMusic. All rights reserved.
  7. //
  8. #import "KSNewWhiteBoard.h"
  9. #import <WeakWebViewScriptMessageDelegate.h>
  10. #import <WebKit/WebKit.h>
  11. #import "CustomNavViewController.h"
  12. #define MAXPHOTONUMBER (1)
  13. #define COLUMNNUMBER (3)
  14. #import "TZImageManager.h"
  15. #import <AssetsLibrary/AssetsLibrary.h>
  16. #import <Photos/Photos.h>
  17. #import "TZVideoPlayerController.h"
  18. #import "TZImagePickerController.h"
  19. #import <UIImage+ResizeImage.h>
  20. #import <UIImage+Color.h>
  21. #import "WhiteUtils.h"
  22. #import "WebViewBaseConfig.h"
  23. #define touchPy 10
  24. #define WHITE_SCRIPT_NAME (@"DAYA")
  25. #define WHITE_AGENT_NAME (@"DAYAAPPI")
  26. #define WHITE_AGENT_DOMAIN (@"DAYAAPPSTUDENT")
  27. @interface KSNewWhiteBoard ()<WKUIDelegate,WKNavigationDelegate,WKScriptMessageHandler,UIImagePickerControllerDelegate,UINavigationControllerDelegate,TZImagePickerControllerDelegate,UIAlertViewDelegate,UIGestureRecognizerDelegate>
  28. {
  29. BOOL _isSelectOriginalPhoto;
  30. }
  31. @property (nonatomic, strong, nullable) WKWebView *myWebView;
  32. @property (nonatomic, assign) BOOL hasModify;
  33. @property (nonatomic, strong) UIImagePickerController *imagePickerVc;
  34. @property (nonatomic, strong) NSMutableArray *imageArray; // 图片数组
  35. @property (nonatomic, strong) NSMutableArray *imageAsset; // 图片 asset
  36. @property (nonatomic, strong) UIImageView *chooseImage;
  37. @property (nonatomic, assign) CGPoint beginPoint;
  38. @property (nonatomic, weak) id<KSNewWhiteboardViewDelegate> delegate;
  39. @property (nonatomic, assign) BOOL isChoosePhoto;
  40. @property (nonatomic, assign) BOOL isLoadingSource;
  41. @property (nonatomic, assign) BOOL isJoinRoom;
  42. @end
  43. @implementation KSNewWhiteBoard
  44. - (instancetype)initWithDelegate:(id<KSNewWhiteboardViewDelegate>)delegate frame:(CGRect)frame {
  45. self = [super initWithFrame:frame];
  46. if (self) {
  47. self.delegate = delegate;
  48. }
  49. return self;
  50. }
  51. - (void)configView:(CGRect)frame {
  52. [self addSubview:self.myWebView];
  53. self.myWebView.userInteractionEnabled = NO;
  54. [self.myWebView mas_makeConstraints:^(MASConstraintMaker *make) {
  55. make.left.right.top.bottom.mas_equalTo(self);
  56. }];
  57. [self addSubview:self.refreshButton];
  58. [self.refreshButton mas_makeConstraints:^(MASConstraintMaker *make) {
  59. make.left.mas_equalTo(self.myWebView.mas_left).offset(18);
  60. make.top.mas_equalTo(self.myWebView.mas_top).offset(10);
  61. make.width.mas_equalTo(81);
  62. make.height.mas_equalTo(21);
  63. }];
  64. }
  65. - (void)configUserAgent:(WKWebViewConfiguration *)config {
  66. NSString *oldUserAgent = config.applicationNameForUserAgent;
  67. NSString *newAgent = [NSString stringWithFormat:@"%@ %@ %@",oldUserAgent,WHITE_AGENT_NAME,WHITE_AGENT_DOMAIN];
  68. config.applicationNameForUserAgent = newAgent;
  69. }
  70. - (void)loadRequest {
  71. NSLog(@"---------- %@",self.url);
  72. NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:self.url] cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:60.0f];
  73. [self.myWebView loadRequest:request];
  74. }
  75. - (WKWebView *)myWebView {
  76. if (!_myWebView) {
  77. _myWebView = [[WKWebView alloc] initWithFrame:CGRectZero configuration:[self getwebConfig]];
  78. _myWebView.scrollView.bounces = NO;
  79. _myWebView.UIDelegate = self;
  80. _myWebView.navigationDelegate = self;
  81. #ifdef DEBUG
  82. if (@available(iOS 16.4, *)) {
  83. _myWebView.inspectable = YES;
  84. }
  85. #endif
  86. }
  87. return _myWebView;
  88. }
  89. - (WKWebViewConfiguration *)getwebConfig {
  90. WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
  91. config.selectionGranularity = WKSelectionGranularityDynamic;
  92. config.allowsInlineMediaPlayback = YES;
  93. config.mediaTypesRequiringUserActionForPlayback = NO;
  94. config.processPool = [KSNewWhiteBoard singleWkProcessPool];
  95. config.websiteDataStore = [WKWebsiteDataStore defaultDataStore];
  96. [self configUserAgent:config];
  97. //自定义的WKScriptMessageHandler 是为了解决内存不释放的问题
  98. WeakWebViewScriptMessageDelegate *weakScriptMessageDelegate = [[WeakWebViewScriptMessageDelegate alloc] initWithDelegate:self];
  99. //这个类主要用来做native与JavaScript的交互管理
  100. WKUserContentController * wkUController = [[WKUserContentController alloc] init];
  101. [wkUController addScriptMessageHandler:weakScriptMessageDelegate name:WHITE_SCRIPT_NAME];
  102. config.userContentController = wkUController;
  103. WKPreferences *preferences = [WKPreferences new];
  104. // 是否支出javaScript
  105. preferences.javaScriptEnabled = YES;
  106. //不通过用户交互,是否可以打开窗口
  107. preferences.javaScriptCanOpenWindowsAutomatically = YES;
  108. config.preferences = preferences;
  109. return config;
  110. }
  111. + (WKProcessPool*)singleWkProcessPool {
  112. static WKProcessPool *sharedPool;
  113. static dispatch_once_t onceToken;
  114. dispatch_once(&onceToken, ^{
  115. sharedPool = [[WKProcessPool alloc] init];
  116. });
  117. return sharedPool;
  118. }
  119. // 调用js方法
  120. - (void)postMessageJS:(NSDictionary *)jsDict {
  121. dispatch_async(dispatch_get_main_queue(), ^{
  122. NSString *jsString = [jsDict mj_JSONString];
  123. [self.myWebView evaluateJavaScript:[NSString stringWithFormat:@"postMessage(%@,'*')", jsString] completionHandler:nil];
  124. });
  125. }
  126. #pragma mark ----- WKWebView delegate
  127. // 1 在发送请求之前,决定是否跳转
  128. - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
  129. NSLog(@"1-------在发送请求之前,决定是否跳转 -->%@",navigationAction.request);
  130. NSURL *url = navigationAction.request.URL;
  131. NSString *scheme = [url scheme];
  132. UIApplication *app = [UIApplication sharedApplication];
  133. // 打电话
  134. if ([scheme isEqualToString:@"tel"]) {
  135. if ([app canOpenURL:url]) {
  136. [app openURL:url options:@{} completionHandler:nil];
  137. // 一定要加上这句,否则会打开新页面
  138. decisionHandler(WKNavigationActionPolicyCancel);
  139. return;
  140. }
  141. }
  142. decisionHandler(WKNavigationActionPolicyAllow);
  143. }
  144. // 2 页面开始加载时调用
  145. - (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation {
  146. NSLog(@"2-------页面开始加载时调用");
  147. }
  148. // 3 在收到响应后,决定是否跳转
  149. - (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler {
  150. /// 在收到服务器的响应头,根据response相关信息,决定是否跳转。decisionHandler必须调用,来决定是否跳转,参数WKNavigationActionPolicyCancel取消跳转,WKNavigationActionPolicyAllow允许跳转
  151. NSLog(@"3-------在收到响应后,决定是否跳转");
  152. decisionHandler(WKNavigationResponsePolicyAllow);
  153. }
  154. // 4 当内容开始返回时调用
  155. - (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation {
  156. NSLog(@"4-------当内容开始返回时调用");
  157. }
  158. // 5 页面加载完成之后调用
  159. - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
  160. NSLog(@"5-------页面加载完成之后调用");
  161. if (_hasModify == NO) {
  162. [self configLocalStorage];
  163. }
  164. }
  165. - (void)configLocalStorage {
  166. _hasModify = YES;
  167. NSString *jsString = [NSString stringWithFormat:@"localStorage.setItem('Authorization', '%@ %@')",UserDefault(Token_type), UserDefault(TokenKey)];
  168. [self.myWebView evaluateJavaScript:jsString completionHandler:nil];
  169. }
  170. // 6 页面加载失败时调用
  171. - (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error {
  172. NSLog(@"6-------页面加载失败时调用");
  173. [LOADING_MANAGER MBShowAUTOHidingInWindow:@"网页加载失败!"];
  174. }
  175. // 接收到服务器跳转请求之后调用
  176. - (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation {
  177. NSLog(@"-------接收到服务器跳转请求之后调用");
  178. }
  179. // 数据加载发生错误时调用
  180. - (void)webView:(WKWebView *)webView didFailNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error {
  181. NSLog(@"----数据加载发生错误时调用");
  182. [LOADING_MANAGER MBShowAUTOHidingInWindow:@"数据加载发生错误!"];
  183. }
  184. - (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *_Nullable))completionHandler
  185. {
  186. dispatch_queue_t queue = dispatch_queue_create("webViewChallengeQueue", NULL);
  187. dispatch_async(queue, ^{
  188. if (SSL_AUTH) {
  189. NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
  190. NSURLCredential *customCredential = nil;
  191. if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
  192. // 默认信任
  193. customCredential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
  194. disposition = NSURLSessionAuthChallengeUseCredential;
  195. }
  196. else if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodClientCertificate]) {
  197. // client authentication
  198. SecIdentityRef identity = NULL;
  199. SecTrustRef trust = NULL;
  200. if ([AuthChallengeManager extractIdentity:&identity andTrust:&trust filePath:CERT_PATH]) {
  201. SecCertificateRef certificate = NULL;
  202. SecIdentityCopyCertificate(identity, &certificate);
  203. const void*certs[] = {certificate};
  204. CFArrayRef certArray =CFArrayCreate(kCFAllocatorDefault, certs,1,NULL);
  205. customCredential =[NSURLCredential credentialWithIdentity:identity certificates:(__bridge NSArray*)certArray persistence:NSURLCredentialPersistencePermanent];
  206. disposition = NSURLSessionAuthChallengeUseCredential;
  207. // 释放 CFArrayRef 和 SecCertificateRef
  208. CFRelease(certArray);
  209. CFRelease(certificate);
  210. }
  211. }
  212. if (completionHandler) {
  213. completionHandler(disposition, customCredential);
  214. }
  215. }
  216. else {
  217. if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
  218. if (challenge.previousFailureCount == 0) {
  219. NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
  220. completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
  221. } else {
  222. completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
  223. }
  224. }
  225. else {
  226. completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
  227. }
  228. }
  229. });
  230. }
  231. #pragma mark - WKScriptMessageHandler
  232. - (void)userContentController:(WKUserContentController *)userContentController
  233. didReceiveScriptMessage:(WKScriptMessage *)message {
  234. if ([message.name isEqualToString:WHITE_SCRIPT_NAME]) {
  235. NSDictionary *parm = [self convertJsonStringToNSDictionary:message.body];
  236. // 回到主线程
  237. dispatch_async(dispatch_get_main_queue(), ^{
  238. [self handleScriptMessageSource:parm];
  239. });
  240. }
  241. }
  242. - (void)handleScriptMessageSource:(NSDictionary *)parm {
  243. NSLog(@"----%@",parm);
  244. if ([[parm ks_stringValueForKey:@"api"] isEqualToString:@"whiteboardSelectPhoto"]) { // 选择图片
  245. self.isChoosePhoto = YES;
  246. [self pushImagePickerController];
  247. }
  248. else if ([[parm ks_stringValueForKey:@"api"] isEqualToString:@"whiteboardSelectMusicLibrary"]) { // 选择曲库
  249. // [self chooseImageWithLibiary];
  250. }
  251. // 白板连接状态
  252. else if ([[parm ks_stringValueForKey:@"api"] isEqualToString:@"whiteboardConnectStatus"]) {
  253. NSDictionary *valueDic = [parm ks_dictionaryValueForKey:@"content"];
  254. if ([[valueDic ks_stringValueForKey:@"status"] isEqualToString:@"success"]) { // 连接成功
  255. self.connectedSuccess = YES;
  256. }
  257. else { // 连接失败
  258. self.connectedSuccess = NO;
  259. [LOADING_MANAGER MBShowAUTOHidingInWindow:@"白板连接错误,请刷新一下"];
  260. }
  261. }
  262. }
  263. - (NSDictionary *)convertJsonStringToNSDictionary:(NSString *)jsonString {
  264. if (jsonString == nil) {
  265. return nil;
  266. }
  267. NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
  268. NSError *error;
  269. NSDictionary *json = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:&error];
  270. if (error) {
  271. NSLog(@"jsonString解析失败:%@", error);
  272. return nil;
  273. }
  274. return json;
  275. }
  276. - (void)dealloc {
  277. NSLog(@"-white board-");
  278. [[_myWebView configuration].userContentController removeScriptMessageHandlerForName:WHITE_SCRIPT_NAME];
  279. [_myWebView loadHTMLString:@"" baseURL:nil];
  280. [_myWebView removeFromSuperview];
  281. _myWebView = nil;
  282. [self clearWebCache];
  283. // [[NSURLCache sharedURLCache] removeAllCachedResponses];
  284. // [[NSURLCache sharedURLCache] setDiskCapacity:0];
  285. [[NSURLCache sharedURLCache] setMemoryCapacity:0];
  286. [[NSNotificationCenter defaultCenter] removeObserver:self];
  287. }
  288. - (void)clearWebCache {
  289. NSSet *websiteDataTypes = [NSSet setWithArray:@[
  290. WKWebsiteDataTypeMemoryCache,
  291. ]];
  292. NSDate *dateFrom = [NSDate dateWithTimeIntervalSince1970:0];
  293. [[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:websiteDataTypes
  294. modifiedSince:dateFrom
  295. completionHandler:^{
  296. NSLog(@"Caches cleared");
  297. }];
  298. }
  299. #pragma mark ------- 曲库下载或者相册选择
  300. - (void)uploadImage {
  301. UIAlertController *alertVC = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:IS_IPAD ? UIAlertControllerStyleAlert : UIAlertControllerStyleActionSheet];
  302. [alertVC addAction:[UIAlertAction actionWithTitle:@"曲库选择" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
  303. [self chooseImageWithLibiary];
  304. }]];
  305. [alertVC addAction:[UIAlertAction actionWithTitle:@"从手机相册选择" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
  306. // 调用相册
  307. [self pushImagePickerController];
  308. }]];
  309. [alertVC addAction:[UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
  310. }]];
  311. alertVC.modalPresentationStyle = UIModalPresentationFullScreen;
  312. [[self getViewController] presentViewController:alertVC animated:true completion:nil];
  313. }
  314. - (void)chooseImageWithLibiary {
  315. }
  316. #pragma mark - TZImagePickerController
  317. - (void)pushImagePickerController {
  318. TZImagePickerController *imagePickerVc = [[TZImagePickerController alloc] initWithMaxImagesCount:MAXPHOTONUMBER columnNumber:COLUMNNUMBER delegate:self];
  319. #pragma mark - 四类个性化设置,这些参数都可以不传,此时会走默认设置
  320. imagePickerVc.isSelectOriginalPhoto = NO;
  321. // 1.设置目前已经选中的图片数组
  322. imagePickerVc.selectedAssets = self.imageAsset; // 目前已经选中的图片数组
  323. imagePickerVc.allowTakePicture = YES; // 在内部显示拍照按钮
  324. // 2. Set the appearance
  325. // 2. 在这里设置imagePickerVc的外观
  326. [imagePickerVc.navigationBar setBarTintColor:THEMECOLOR];
  327. // 3. Set allow picking video & photo & originalPhoto or not
  328. // 3. 设置是否可以选择视频/图片/原图
  329. imagePickerVc.allowPickingVideo = NO;
  330. imagePickerVc.allowPickingImage = YES;
  331. imagePickerVc.allowPickingOriginalPhoto = NO;
  332. imagePickerVc.allowCrop = NO;
  333. imagePickerVc.needCircleCrop = NO;
  334. imagePickerVc.showSelectBtn = YES;
  335. NSInteger left = 30;
  336. NSInteger widthHeight = kScreenWidth - 2 * left;
  337. NSInteger top = (kScreenHeight - widthHeight) / 2;
  338. imagePickerVc.cropRect = CGRectMake(left, top, widthHeight, widthHeight);
  339. // 4. 照片排列按修改时间升序
  340. imagePickerVc.sortAscendingByModificationDate = NO;
  341. #pragma mark - 到这里为止
  342. // You can get the photos by block, the same as by delegate.
  343. // 你可以通过block或者代理,来得到用户选择的照片.
  344. [imagePickerVc setDidFinishPickingPhotosHandle:^(NSArray<UIImage *> *photos, NSArray *assets, BOOL isSelectOriginalPhoto) {
  345. // 赋值
  346. [self evaluateWithChooseImage:[photos lastObject] scaleWidth:NO];
  347. }];
  348. imagePickerVc.modalPresentationStyle = UIModalPresentationFullScreen;
  349. [[self getViewController] presentViewController:imagePickerVc animated:YES completion:nil];
  350. }
  351. // 图片选择回调
  352. - (void)evaluateWithChooseImage:(UIImage *)image scaleWidth:(BOOL)needScaleWidth {
  353. dispatch_async(dispatch_get_main_queue(), ^{
  354. [self uploadImage:image scaleWidth:needScaleWidth];
  355. });
  356. }
  357. - (void)uploadImage:(UIImage *)image scaleWidth:(BOOL)needScaleWidth {
  358. // 上传图片 回调显示
  359. NSData *imgData = [UIImage compressImage:image maxLength:5];
  360. NSString *fileName = @"whiteboardImage";
  361. [UPLOAD_MANAGER configWithfilePath:@"/whiteboard/"];
  362. [UPLOAD_MANAGER uploadImage:imgData fileName:fileName successCallback:^(NSMutableArray * _Nonnull fileUrlArray) {
  363. NSString *imageUrl = [fileUrlArray lastObject];
  364. [self insertImageWithUrl:imageUrl size:image.size];
  365. } faliure:^(NSError * _Nullable error, NSString * _Nullable descMessaeg) {
  366. if (![NSString isEmptyString:descMessaeg]) {
  367. [LOADING_MANAGER MBShowAUTOHidingInWindow:descMessaeg];
  368. }
  369. }];
  370. }
  371. - (void)insertImageWithUrl:(NSString *)imageUrl size:(CGSize)imageSize {
  372. NSMutableDictionary *parm = [NSMutableDictionary dictionary];
  373. if (_isChoosePhoto) {
  374. self.isChoosePhoto = NO;
  375. [parm setValue:@"whiteboardSelectPhoto" forKey:@"api"];
  376. }
  377. else {
  378. [parm setValue:@"whiteboardSelectMusicLibrary" forKey:@"api"];
  379. }
  380. NSMutableDictionary *content = [NSMutableDictionary dictionary];
  381. [content setValue:imageUrl forKey:@"url"];
  382. [parm setValue:content forKey:@"content"];
  383. [self postMessageJS:parm];
  384. }
  385. #pragma mark - UIImagePickerController
  386. - (void)takePhoto {
  387. AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
  388. if ((authStatus == AVAuthorizationStatusRestricted || authStatus == AVAuthorizationStatusDenied) && kiOS7Later) {
  389. // 无权限 做一个友好的提示
  390. #pragma clang diagnostic push
  391. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  392. UIAlertView * alert = [[UIAlertView alloc]initWithTitle:@"无法使用相机" message:@"请在iPhone的""设置-隐私-相机""中允许访问相机" delegate:self cancelButtonTitle:@"取消" otherButtonTitles:@"设置", nil];
  393. [alert show];
  394. #pragma clang diagnostic pop
  395. }
  396. else if (authStatus == AVAuthorizationStatusNotDetermined) {
  397. // 防止用户首次拍照拒绝授权时相机页黑屏
  398. [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
  399. if (granted) {
  400. dispatch_async(dispatch_get_main_queue(), ^{
  401. [self takePhoto];
  402. });
  403. }
  404. }];
  405. }
  406. else if ([PHPhotoLibrary authorizationStatus] == 2) { // 已被拒绝,没有相册权限,将无法保存拍的照片
  407. #pragma clang diagnostic push
  408. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  409. UIAlertView * alert = [[UIAlertView alloc]initWithTitle:@"无法访问相册" message:@"请在iPhone的""设置-隐私-相册""中允许访问相册" delegate:self cancelButtonTitle:@"取消" otherButtonTitles:@"设置", nil];
  410. [alert show];
  411. #pragma clang diagnostic pop
  412. }
  413. else if ([PHPhotoLibrary authorizationStatus] == 0) { // 未请求过相册权限
  414. [[TZImageManager manager] requestAuthorizationWithCompletion:^{
  415. [self takePhoto];
  416. }];
  417. }
  418. else { // 调用相机
  419. UIImagePickerControllerSourceType sourceType = UIImagePickerControllerSourceTypeCamera;
  420. if ([UIImagePickerController isSourceTypeAvailable: UIImagePickerControllerSourceTypeCamera]) {
  421. self.imagePickerVc.sourceType = sourceType;
  422. if(kiOS8Later) {
  423. _imagePickerVc.modalPresentationStyle = UIModalPresentationFullScreen;
  424. }
  425. _imagePickerVc.modalPresentationStyle = UIModalPresentationFullScreen;
  426. [[self getViewController] presentViewController:_imagePickerVc animated:YES completion:nil];
  427. } else {
  428. NSLog(@"模拟器中无法打开照相机,请在真机中使用");
  429. }
  430. }
  431. }
  432. // 外部拍照进入的方法
  433. - (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
  434. [picker dismissViewControllerAnimated:YES completion:nil];
  435. NSString *type = [info objectForKey:UIImagePickerControllerMediaType];
  436. if ([type isEqualToString:@"public.image"]) {
  437. TZImagePickerController *tzImagePickerVc = [[TZImagePickerController alloc] initWithMaxImagesCount:MAXPHOTONUMBER delegate:self];
  438. tzImagePickerVc.allowCrop = YES;
  439. tzImagePickerVc.needCircleCrop = NO;
  440. tzImagePickerVc.showSelectBtn = YES;
  441. tzImagePickerVc.sortAscendingByModificationDate = NO;
  442. [tzImagePickerVc showProgressHUD];
  443. UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage];
  444. // save photo and get asset / 保存图片,获取到asset
  445. [[TZImageManager manager] savePhotoWithImage:image completion:^(PHAsset *asset,NSError *error){
  446. if (error) { // 如果保存失败,基本是没有相册权限导致的...
  447. [tzImagePickerVc hideProgressHUD];
  448. NSLog(@"图片保存失败 %@",error);
  449. UIAlertView * alert = [[UIAlertView alloc]initWithTitle:@"无法保存图片" message:@"请在iPhone的""设置-隐私-相册""中允许访问相册" delegate:self cancelButtonTitle:@"取消" otherButtonTitles:@"设置", nil];
  450. alert.tag = 1;
  451. [alert show];
  452. } else {
  453. TZAssetModel *assetModel = [[TZImageManager manager] createModelWithAsset:asset];
  454. TZImagePickerController *imagePicker = [[TZImagePickerController alloc] initCropTypeWithAsset:assetModel.asset photo:image completion:^(UIImage *cropImage, id asset) {
  455. // 回调
  456. [self evaluateWithChooseImage:cropImage scaleWidth:NO];
  457. }];
  458. imagePicker.allowPickingImage = YES;
  459. imagePicker.allowCrop = YES;
  460. imagePicker.needCircleCrop = NO;
  461. NSInteger left = 30;
  462. NSInteger widthHeight = kScreenWidth - 2 * left;
  463. NSInteger top = (kScreenHeight - widthHeight) / 2;
  464. imagePicker.cropRect = CGRectMake(left, top, widthHeight, widthHeight);
  465. imagePicker.modalPresentationStyle = UIModalPresentationFullScreen;
  466. [[self getViewController] presentViewController:imagePicker animated:YES completion:nil];
  467. }
  468. }];
  469. }
  470. }
  471. - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker {
  472. if ([picker isKindOfClass:[UIImagePickerController class]]) {
  473. [picker dismissViewControllerAnimated:YES completion:nil];
  474. }
  475. }
  476. #pragma mark - getter
  477. - (NSMutableArray *)imageArray {
  478. if (!_imageArray) {
  479. _imageArray = [NSMutableArray array];
  480. }
  481. return _imageArray;
  482. }
  483. - (NSMutableArray *)imageAsset {
  484. if (!_imageAsset) {
  485. _imageAsset = [NSMutableArray array];
  486. }
  487. return _imageAsset;
  488. }
  489. - (UIImagePickerController *)imagePickerVc {
  490. if (_imagePickerVc == nil) {
  491. _imagePickerVc = [[UIImagePickerController alloc] init];
  492. _imagePickerVc.delegate = self;
  493. // set appearance / 改变相册选择页的导航栏外观
  494. _imagePickerVc.navigationBar.barTintColor = THEMECOLOR;
  495. _imagePickerVc.navigationBar.tintColor = THEMECOLOR;
  496. UIBarButtonItem *tzBarItem, *BarItem;
  497. if (kiOS9Later) {
  498. tzBarItem = [UIBarButtonItem appearanceWhenContainedInInstancesOfClasses:@[[TZImagePickerController class]]];
  499. BarItem = [UIBarButtonItem appearanceWhenContainedInInstancesOfClasses:@[[UIImagePickerController class]]];
  500. } else {
  501. #pragma clang diagnostic push
  502. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  503. tzBarItem = [UIBarButtonItem appearanceWhenContainedIn:[TZImagePickerController class], nil];
  504. BarItem = [UIBarButtonItem appearanceWhenContainedIn:[UIImagePickerController class], nil];
  505. #pragma clang diagnostic pop
  506. }
  507. NSDictionary *titleTextAttributes = [tzBarItem titleTextAttributesForState:UIControlStateNormal];
  508. [BarItem setTitleTextAttributes:titleTextAttributes forState:UIControlStateNormal];
  509. }
  510. return _imagePickerVc;
  511. }
  512. #pragma mark - UIAlertViewDelegate
  513. #pragma clang diagnostic push
  514. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  515. #pragma clang diagnostic ignored "-Wdeprecated-implementations"
  516. - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
  517. if (buttonIndex == 1) { // 去设置界面,开启相机访问权限
  518. [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]];
  519. }
  520. }
  521. #pragma clang diagnostic pop
  522. - (UIImageView *)chooseImage {
  523. if (!_chooseImage) {
  524. _chooseImage = [[UIImageView alloc] init];
  525. }
  526. return _chooseImage;
  527. }
  528. /*
  529. // Only override drawRect: if you perform custom drawing.
  530. // An empty implementation adversely affects performance during animation.
  531. - (void)drawRect:(CGRect)rect {
  532. // Drawing code
  533. }
  534. */
  535. - (void)setCurrentFrame:(CGRect)currentFrame {
  536. _currentFrame = currentFrame;
  537. self.frame = currentFrame;
  538. }
  539. - (void)destroy {
  540. }
  541. // 加入白板房间
  542. - (void)joinWhiteRoom {
  543. [self configView:self.bounds];
  544. if (self.url) {
  545. [self loadRequest];
  546. }
  547. else {
  548. self.isJoinRoom = YES;
  549. [LOADING_MANAGER showCustomLoading:@"加载中..."];
  550. }
  551. }
  552. // 离开房间
  553. - (void)leaveRoom {
  554. self.isJoinRoom = NO;
  555. [self removeLoadig];
  556. [[_myWebView configuration].userContentController removeScriptMessageHandlerForName:WHITE_SCRIPT_NAME];
  557. [_myWebView loadHTMLString:@"" baseURL:nil];
  558. [_myWebView removeFromSuperview];
  559. _myWebView = nil;
  560. [self clearWebCache];
  561. // [[NSURLCache sharedURLCache] removeAllCachedResponses];
  562. // [[NSURLCache sharedURLCache] setDiskCapacity:0];
  563. [[NSURLCache sharedURLCache] setMemoryCapacity:0];
  564. [[NSNotificationCenter defaultCenter] removeObserver:self];
  565. }
  566. - (void)showLoading {
  567. self.isLoadingSource = YES;
  568. }
  569. - (void)removeLoadig {
  570. self.isLoadingSource = NO;
  571. if (self.isJoinRoom) {
  572. [LOADING_MANAGER removeCustomLoading];
  573. [self loadRequest];
  574. }
  575. }
  576. - (void)setConnectedSuccess:(BOOL)connectedSuccess {
  577. _connectedSuccess = connectedSuccess;
  578. if (connectedSuccess) { // 连接成功
  579. self.refreshButton.hidden = YES;
  580. }
  581. else { // 连接失败
  582. self.refreshButton.hidden = NO;
  583. }
  584. }
  585. - (KSWhiteboardRefreshView *)refreshButton {
  586. if (!_refreshButton) {
  587. _refreshButton = [KSWhiteboardRefreshView shareInstance];
  588. MJWeakSelf;
  589. [_refreshButton refreshActionCallback:^{
  590. [weakSelf refreshWebViewAction];
  591. }];
  592. }
  593. return _refreshButton;
  594. }
  595. - (void)refreshWebViewAction {
  596. // 如果链接为空
  597. if ([NSString isEmptyString:self.url]) {
  598. [LOADING_MANAGER showCustomLoading:@"加载中..."];
  599. [WhiteUtils getRoomTokenWithRoomId:self.roomId completionHandler:^(NSString * _Nullable uuid, NSString * _Nullable roomToken, NSString * _Nonnull randomRoom, NSString * _Nullable randomNumeric, NSError * _Nullable error) {
  600. [LOADING_MANAGER removeCustomLoading];
  601. if (!error) {
  602. NSString *url = [NSString stringWithFormat:@"%@%@?roomId=%@&userId=%@&clientType=STUDENT&role=viewer#room=%@,%@", hostURL, @"/whiteboard/",self.roomId,UserDefault(UIDKey),randomRoom,randomNumeric];
  603. self.url = url;
  604. [self refreshRequest];
  605. }
  606. }];
  607. }
  608. else { // 刷新webView
  609. [self refreshRequest];
  610. }
  611. }
  612. - (void)refreshRequest {
  613. [LOADING_MANAGER showCustomLoading:@"加载中..."];
  614. [self loadRequest];
  615. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  616. [LOADING_MANAGER removeCustomLoading];
  617. });
  618. }
  619. - (UIViewController *)getViewController {
  620. for (UIView *view = self; view; view = view.superview) {
  621. UIResponder *nextResponder = [view nextResponder];
  622. if ([nextResponder isKindOfClass:[UIViewController class]]) {
  623. return (UIViewController *)nextResponder;
  624. }
  625. }
  626. return nil;
  627. }
  628. @end