一、学习笔记
-
iOS
自定义View
:-
如果一个
view
内部的子控件比较多,可以将其封装为一个自定义view
,基本流程如下:- 重写
- (instancetype)initWithFrame:(CGRect)frame
方法,在该方法中添加子控件(或者使用懒加载),注意此时不需要设置子控件的frame
- 重写
- (void)layoutSubviews
方法,在该方法中设置子控件的frame
,注意必须调用[super layoutSubviews];
- 定义数据模型属性,并重写该属性的
set
方法,在set
方法中取出相应的属性并赋给子控件 - 如果涉及到对子控件的事件处理,则定义
Block
属性,并定义对应的set
方法,在使用该view
的父view
的controller
设置这些Block
属性,作为内控事件的回调
- 重写
-
示例:
// .h文件 #import <UIKit/UIKit.h> #import "CustomUIViewModel.h" @interface CustomUIView : UIView // 数据模型 @property (nonatomic, strong) CustomUIViewModel *model; // 点击回调的Block @property (nonatomic, copy) void(^labelClick)(void); // 设置Block的方法 - (void)setLabelClickWithBlock:(void(^)(void)) labelClickBlock; @end // .m文件 #import "CustomUIView.h" @interface CustomUIView() @property (nonatomic, strong) UILabel *label; @end @implementation CustomUIView // 1.重写initWithFrame:方法,创建子控件并添加到view里 - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { UILabel *label = [[UILabel alloc] init]; // 4.添加TapGesture label.userInteractionEnabled = YES; UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(labelTapGesture:)]; [label addGestureRecognizer:tap]; self.label = label; [self addSubview:label]; } return self; } // 2.重写layoutSubviews,给子控件设置frame - (void)layoutSubviews { [super layoutSubviews]; CGSize size = self.frame.size; self.label.frame = CGRectMake(0, 0, size.width * 0.5, size.height * 0.5); } // 3.重写数据模型属性的set方法,给子控件赋值 - (void)setModel:(CustomUIViewModel *)model { _model = model; self.label.text = model.name; } // 4.在TapGesture中调用回调的Block - (void)labelTapGesture:(UITapGestureRecognizer *)gestureRecognizer { if (self.labelClick) { self.labelClick(); } } // 4.设置Block - (void)setLabelClickWithBlock:(void(^)(void)) labelClickBlock { _labelClick = labelClickBlock; } @end
-
两个问题:
-
为什么自定义
view
时重写- (instancetype)initWithFrame:(CGRect)frame
而不是- (instancetype)init
?- 因为在创建该自定义
view
的时候,可能使用init
也可能使用initWithFrame
,但是无论使用哪个,在代码执行的过程中最终一定会调用initWithFrame
- 因为在创建该自定义
-
为什么只是在
- (instancetype)initWithFrame:(CGRect)frame
方法中添加子控件而不设置frame
,而是在- (void)layoutSubviews
设置子控件的frame
?-
layoutSubViews
方法在以下情况会被触发:- 使用
init
初始化时不会触发layoutSubviews
,但是是用initWithFrame
进行初始化时,当rect
的值不为CGRectZero
时,会触发 addSubview
会触发layoutSubviews
- 设置
view
的Frame
会触发layoutSubviews
,当然前提是frame
的值设置前后发生了变化 - 滚动一个
UIScrollView
会触发layoutSubviews
- 旋转
Screen
会触发父UIView
上的layoutSubviews
事件 - 改变一个
UIView
大小的时候也会触发父UIView
上的layoutSubviews
事件
- 使用
-
那么如果在
initWithFrame
方法里设置子控件的frame
,使用init
方法初始化时,比如CustomUIView *viewWithInit = [[CustomUIView alloc]init]; viewWithInit.frame = CGRectMake(10, 300, 300, 100);
虽然
init
方法最终也会调用initWithFrame
方法,但是因为此时这个view
的frame
还没有设置,self = [super initWithFrame:frame]
的结果是一个frame = (0 0; 0 0)
的view
,此时设置子控件的frame
是无效的,所以需要在设置了view
的frame
之后再对子控件重新布局
-
-
-
-
SDWebImage
-
基本方法:
// sd_setImageWithURL 设置图片 url [self.image sd_setImageWithURL:imagePath]; // completed 设置图片加载完后回调的 block [self.image sd_setImageWithURL:imagePath completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) { // ... }]; // placeholderImage 设置图片加载出来之前使用的默认图片 [self.image sd_setImageWithURL:imagePath placeholderImage:[UIImage imageNamed:@"default"]]; // options 设置缓存的模式(默认是内存缓存和磁盘缓存结合) [self.image sd_setImageWithURL:imagePath placeholderImage:[UIImage imageNamed:@"default"] options:SDWebImageRetryFailed];
-
options
的可选项:// 失败后重试 SDWebImageRetryFailed = 1 << 0, // UI交互期间开始下载,会导致延迟下载比如UIScrollView减速 SDWebImageLowPriority = 1 << 1, // 只进行内存缓存 SDWebImageCacheMemoryOnly = 1 << 2, // 这个标志可以渐进式下载,显示的图像是逐步在下载 SDWebImageProgressiveDownload = 1 << 3, // 刷新缓存 SDWebImageRefreshCached = 1 << 4, // 后台下载 SDWebImageContinueInBackground = 1 << 5, // NSMutableURLRequest.HTTPShouldHandleCookies = YES; SDWebImageHandleCookies = 1 << 6, // 允许使用无效的SSL证书 SDWebImageAllowInvalidSSLCertificates = 1 << 7, // 优先下载 SDWebImageHighPriority = 1 << 8, // 延迟占位符 SDWebImageDelayPlaceholder = 1 << 9, // 改变动画形象 SDWebImageTransformAnimatedImage = 1 << 10,
-
SDWebImage
内部实现过程:- 以
sd_setImageWithURL:placeholderImage:options:
为例,会先显示placeholderImage
,然后SDWebImageManager
根据URL
开始处理图片 SDWebImageManager
调用downloadWithURL:delegate:options:userInfo:
,调用SDImageCache
的queryDiskCacheForKey:delegate:userInfo:
方法从缓存查找图片是否已经下载- 先到内存缓存中查找是否有图片缓存,如果有则
SDImageCacheDelegate
回调imageCache:didFindImage:forKey:userInfo:
到SDWebImageManager
SDWebImageManagerDelegate
回调webImageManager:didFinishWithImage:
到UIImageView+WebCache
等前端展示图片- 如果内存缓存中没有图片缓存,则生成 NSInvocationOperation 添加到队列,根据 URLKey 在硬盘缓存目录下尝试读取图片文件,然后回主线程进行结果回调
notifyDelegate:
- 如果从硬盘读取到了图片,则将图片添加到内存缓存中(如果空闲内存过小,会先清空内存缓存),
SDImageCacheDelegate
回调imageCache:didFindImage:forKey:userInfo:
,进而回调展示图片 - 如果从硬盘缓存目录读取不到图片,说明所有缓存都不存在该图片,需要下载图片,回调
imageCache:didNotFindImageForKey:userInfo:
,共享或重新生成一个下载器SDWebImageDownloader
开始下载图片 - 图片下载用的是
NSURLConnection
,并实现了相关delegate
来判断图片下载中、下载完成和下载失败,connection:didReceiveData:
中利用ImageIO
做了按图片下载进度加载效果 connectionDidFinishLoading:
数据下载完成后交给SDWebImageDecoder
做图片解码处理,在一个NSOperationQueue
完成,不会拖慢主线程- 在主线程中
notifyDelegateOnMainThreadWithInfo:
宣告解码完成,imageDecoder:didFinishDecodingImage:userInfo:
回调给SDWebImageDownloader
imageDownloader:didFinishWithImage:
回调给SDWebImageManager
告知图片下载完成- 通知所有的
downloadDelegates
下载完成,回调给需要的地方展示图片 - 将图片保存到
SDImageCache
中,内存缓存和硬盘缓存同时保存,写文件到硬盘也在以单独NSInvocationOperation
完成,不会拖慢主线程 SDImageCache
在初始化的时候会注册一些消息通知,在内存警告或退到后台的时候清理内存图片缓存,应用结束的时候清理过期图片- 可以覆盖其中的某些方法实现自己需要的功能
- 以
-
-
WKWebView
中JS
和OC
的交互:-
OC
调用JS
:- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ _Nullable)(_Nullable id, NSError * _Nullable error))completionHandler;
-
JS
调用OC
:// 在JS中调用原生方法 window.webkit.messageHandlers.<#对象名#>.postMessage(<#参数#>) // 配置控制器,添加scriptMessageHandler [[self.feeeContentWebView configuration].userContentController addScriptMessageHandler:self name:@"<#对象名#>"]; // 实现WKScriptMessageHandler的userContentController方法,响应对应的方法 - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message { if ([message.name isEqualToString:@"<#对象名#>"]) { NSLog(@"%@", message.body); // <#参数#> // ... } }
-
-
WKWebView
中的图片实现本地预览:-
基本的思路是应用上面讲到的
OC
与JS
的交互,在点击HTML
中的图片时,就调用原生方法传回来图片的src
,然后下载图片,放到一个全屏大小的UIImageView
上// 配置控制器 [[self.feeeContentWebView configuration].userContentController addScriptMessageHandler:self name:@"imageClick"]; #pragma mark - WKNavigationDelegate // JS调用OC - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation { [webView evaluateJavaScript:@"function assignImageClickAction(){var imgs=document.getElementsByTagName('img');var length=imgs.length;for(var i=0;i<length;i++){img=imgs[i];img.οnclick=function(){window.webkit.messageHandlers.imageClick.postMessage(this.src)}}}" completionHandler:nil]; [webView evaluateJavaScript:@"assignImageClickAction();" completionHandler:nil]; } #pragma mark - WKScriptMessageHandler // OC调用JS - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message { if ([message.name isEqualToString:@"imageClick"]) { [self.previewImageView sd_setImageWithURL:message.body completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) { self.previewImageView.backgroundColor = [UIColor blackColor]; [self.view bringSubviewToFront:self.previewImageView]; }]; } }
-
-
WKWebView
调整HTML
中的文字两端对齐:-
通过在
htmlString
中添加CSS
样式实现<div style="text-align:justify; text-justify:inter-ideograph;">
-
二、遇到的问题及解决方法
-
合并代码后报错:
:-1: Unable to load contents of file list: 'xxxxx/Pods/Target Support Files/Pods-xxxx/Pods-xxxxx-frameworks-Debug-input-files.xcfilelist' (in target 'xxxxx') :-1: Unable to load contents of file list: 'xxxxx/Pods/Target Support Files/Pods-xxxxx/Pods-xxxxx-frameworks-Debug-output-files.xcfilelist' (in target 'xxxxx')
-
在网上找到的一种解决方法说的是
cocoapods
的版本不一致,但是更新后发现还是报错 -
在
srack overflow
上找到的另一种方法有效,解决方案如下:1.sudo gem update cocoapods --pre 2.pod update 3.clean 4.build
-
三、参考链接
- HTML图片本地预览
- WKWebView OC与JS交互
- SDWebImage
- Unable to load contents of file list: ‘xxxxx/Pods/Target Support Files/Pods-xxxx/Pods-- xxxxx-frameworks-Debug-input-files.xcfilelist’ (in target ‘xxxxx’)
- html文字两端对齐
- 自定义view的封装