1、KVO的含义
2、简单的原理
3、实现注意事项
4、 主要步骤
5、具体代码
例子 1:
代码中,创建WKWebView,在控制器中创建观察者来观察WKWebView的estimaterdProgress属性,一旦属性数据发生改变就收到观察者收到通知,通过
KVO 再在控制器使用回调方法处理实现自定义的Progress的值的变化;
创建新的工程 — 在WebViewController.m 中初始化WKWebView
//
// WebViewController.m
// KVOandKVC
//
// Created by on 2018/11/14.
// Copyright © 2018年 MAC. All rights reserved.
//
#import "ViewController.h"
#import
@interface ViewController ()
@property (nonatomic, strong) UIProgressView * myProgress;
@property (nonatomic, strong) WKWebView * wkView;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self.view addSubview:self.wkView];
[self.wkView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://www.baidu.com"]]];
//注册观察者
/**
注册观察者
@param NSObject self(观察者)
@param keyPath:被观察的属性名称(这里是WKWebView的属性estimatedProgress)
@param options:观察属性的新值、旧值等的一些配置NSKeyValueObservingOptionOld 以字典的形式提供 “初始对象数据”;NSKeyValueObservingOptionNew 以字典的形式提供 “更新后新的数据”
@param context 可以为 KVO 的回调方法传值(例如设定为一个放置数据的字典)
*/
[self.wkView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew context:nil];
}
#pragma 懒加载
- (WKWebView *)wkView {
if (!_wkView) {
// 进行配置控制器
_wkView = [[WKWebView alloc] initWithFrame:CGRectMake(0, 64, self.view.frame.size.width, self.view.frame.size.height - 64) ];
_wkView.navigationDelegate = self;
_wkView.opaque = NO;
_wkView.backgroundColor = [UIColor whiteColor];
if (@available(ios 11.0,*)){ _wkView.scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;}
}
return _wkView;
}
- (UIProgressView *)myProgress {
if (!_myProgress) {
_myProgress = [[UIProgressView alloc] initWithFrame:CGRectMake(0, 100 , [UIScreen mainScreen].bounds.size.width, 2)];
_myProgress.backgroundColor = [UIColor greenColor];
_myProgress.transform = CGAffineTransformMakeScale(1.0f, 1.5f);
_myProgress.progressTintColor = [UIColor blueColor];
[self.view addSubview:_myProgress];
}
return _myProgress;
}
/**
属性(keyPath)的值发生变化时,收到通知,调用以下方法:
@param keyPath 属性的名字
@param object 被观察的对象
@param change 变化前后的值都存储在 change 字典中
@param context 注册观察者时,context 传过来的值
*/
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (object == self.wkView && [keyPath isEqualToString:@"estimatedProgress"]) {
self.myProgress.progress = self.wkView.estimatedProgress;
if (self.myProgress.progress == 1)
{
__weak typeof(self) weakSelf = self;
[UIView animateWithDuration:0.25f delay:0.3f options:UIViewAnimationOptionCurveEaseOut animations:^
{
weakSelf.myProgress.transform = CGAffineTransformMakeScale(1.0f, 1.4f);
}
completion:^(BOOL finished)
{
weakSelf.myProgress.hidden = YES;
}];
}
}
}
- (void)dealloc {
//在dealloc找那个删除Observer
[self.wkView removeObserver:self forKeyPath:@"estimatedProgress"];
NSLog(@"%@内存释放",self);
}
// 页面开始加载时调用
-(void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation{
NSLog(@"页面开始加载时调用");
self.myProgress.hidden = NO;
self.myProgress.transform = CGAffineTransformMakeScale(1.0f, 1.5f);
[self.view bringSubviewToFront:self.myProgress];
}
// 当内容开始返回时调用
- (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation{
NSLog(@"当内容开始返回时调用");
}
// 页面加载完成之后调用
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation{//这里修改导航栏的标题,动态改变
NSLog(@"页面加载完成之后调用");
self.myProgress.hidden = YES;
}
// 页面加载失败时调用
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation{
NSLog(@"页面加载失败时调用");
self.myProgress.hidden = YES;
}
- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error {
if(error.code==NSURLErrorCancelled)
{
[self webView:webView didFinishNavigation:navigation];
}
else
{
self.myProgress.hidden = YES;
}
}
// 接收到服务器跳转请求之后再执行
- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation{
NSLog(@"接收到服务器跳转请求之后再执行");
}
// 在收到响应后,决定是否跳转
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler{
NSLog(@"在收到响应后,决定是否跳转");
NSLog(@"%@",navigationResponse);
WKNavigationResponsePolicy actionPolicy = WKNavigationResponsePolicyAllow;
//这句是必须加上的,不然会异常
decisionHandler(actionPolicy);
}
// 在发送请求之前,决定是否跳转
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{
NSLog(@"在发送请求之前,决定是否跳转");
self.title = webView.title;
WKNavigationActionPolicy actionPolicy = WKNavigationActionPolicyAllow;
//这句是必须加上的,不然会异常
decisionHandler(actionPolicy);
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
@end
例子 2: 可以通过观察model中属性age的变化,来改变View的变化
1、在ViewController.m 中 定义一个 UILable 用来显示年龄 ,一个button 用来修改年龄
2、创建数据模型 LHModel
LHModel.h
#import
@interface LHModel : NSObject
@property (nonatomic, copy) NSString * age;
@end
LHModel.m
#import "LHModel.h"
@implementation LHModel
@synthesize age;
@end
3、在 ModelViewController 中监听并响应属性改变。
#import "ModelViewController.h"
#import "LHModel.h"
@interface ModelViewController ()
{
UILabel * ageLable;
}
@property (strong, nonatomic) LHModel * model;
@end
@implementation ModelViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
ageLable = [[UILabel alloc] initWithFrame:CGRectMake(0, 100, [UIScreen mainScreen].bounds.size.width, 200)];
ageLable.textAlignment = NSTextAlignmentCenter;
ageLable.text = @"显示年龄";
[self.view addSubview:ageLable];
UIButton * btn = [UIButton buttonWithType:UIButtonTypeCustom];
btn.backgroundColor = [UIColor redColor];
[btn setTitle:@"修改年龄" forState:UIControlStateNormal];
btn.frame = CGRectMake(0, 360, [UIScreen mainScreen].bounds.size.width, 60);
[self.view addSubview:btn];
[btn addTarget:self action:@selector(btnClick) forControlEvents:UIControlEventTouchUpInside];
self.model = [[LHModel alloc] init];
//注册观察者
[self.model addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];
}
//收到通知
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (self.model == object && [keyPath isEqualToString:@"age"]) {
ageLable.text = [NSString stringWithFormat:@"%@", [change valueForKey:@"new"]];
}
}
- (void)btnClick {
self.model.age = [NSString stringWithFormat:@"%u",arc4random()%100];
}
- (void)dealloc {
[self.model removeObserver:self forKeyPath:@"age"];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
@end
KVO demo 下载