PNChart之源码分析及使用

PNChart is a simple and beautiful chart lib with animation.
支持LineChart(折线图),PieChart(饼图),BarChart(柱状图),CircleChart(环形图),RadarChart(雷达图),ScatterChart(散点图) 六种图表类型。

##源码分析
先看一下PNChart的类列表:
PNChart.png

很容易看出,除了PNBartChart和PNCircleChart,其他每种类型的图表都有自己对应的数据类,之所以BarChart和CircleChart没有数据类也是因为条形图和环形图表现的数据形式比较单一,只需要传入数据或者百分比即可根据横纵坐标绘制出完整的图表。

先看几个类:

  • PNChart:导入所有类的头文件,方便在使用的时候直接导入PNChart即可。
  • PNColor:使用宏定义了颜色值方便绘图时直接使用。
  • PNChartLabel:继承UILabel定义的一个简单Label。
  • PNChartDelegate:所有图表的点击事件回调代理都统一写在这里。
  • PNGenericChart:所有图表类的父类,负责图例是否显示以及样式等设置。每个子类都需要重写getLegendWithMaxWidth方法来返回各自的图例视图。

接下来我们以最常用的折线图为例,详细分析下源码:
相关的类有三个分别是:PNLineChart,PNLineChartData,PNLineChartDataItem。其中PNLineChartData和PNLineChartDataItem都是数据类,继承自NSObject,为什么需要两个数据类,因为PNLineChart支持同一张表里绘制多条折线,所以,PNLineChartData可以理解为每条折线的数据类,PNLineChartDataItem可以理解为每个点的数据类。

###PNLineChartDataItem
PNLineChartDataItem类的重要属性只有一个

1
@property (readonly) CGFloat y; // should be within the y range

通过类方法:

1
+ (PNLineChartDataItem *)dataItemWithY:(CGFloat)y;

即可创建点对象。

###PNLineChartData
每条线的颜色,透明度,节点数量,标题,是否显示节点标签,节点标签字体,节点标签颜色,节点标签格式,节点的样式,节点颜色,节点的宽度,折线的宽度这些属性可以在PNLineChartData对象来设置。

1
2
3
typedef PNLineChartDataItem *(^LCLineChartDataGetter)(NSUInteger item);

@property (copy) LCLineChartDataGetter getData;

在PNLineChartData中定义了一个获取节点数据的Block:LCLineChartDataGetter,它根据索引item,返回对应数据源的PNLineChartDataItem类对象。

###PNLineChart
PNLineChart这个类才是真正绘制图表类。strokeChart和updateChartData:方法是重要的绘制方法,他们会调用calculateChartPath:andPointsPath:andPathKeyPoints:andPathStartEndPoints: 去根据数据源具体的绘制每一条折线。

PNLineChart中还实现了对视图点击事件的处理,获取了用户接触的节点后,通过PNChartDelegate代理方法回调到控制器里去实现用户需要的操作。

##如何使用
如何使用在PNChart的git主页,作者已经给出用例了,这里就不赘述了。直接附上连接:PNChart

dispatch_semaphore

我为什么要用信号量?
因为一个需求。

需求介绍

这是一个很常见的需求:项目中的业务接口请求的时候需要Token验证。我们最简化这个需求就是:两个请求,请求1成功返回所需参数之后,才能开始请求2。

一个很容易想到的做法就是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
- (void)getToken
{
    //以上请求的设置忽略
    NSURLSessionDataTask *task = [mySession dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if (data) {
            NSLog(@"get Token");
//拿到token,传给request请求做参数
            [self request:token];
        }else{
            NSLog(@"token error:%@",error.description);
        } 
    }];
    [task resume];
}

- (void)request:(NSString *)params
{
    //请求的设置忽略
    NSURLSessionDataTask *task = [mySession dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if (data) {
            NSLog(@"request success");
        }else{
            NSLog(@"request error:%@----",error.description);
        }
    }];
    [task resume];
}

这种做法是最容易想到的,但是缺点也是很明显的。获取token的方法与业务层的请求紧密混合在一起了,而且很不容易分离,这会导致你的网络管理类的请求方法变得臃肿。

所以,下面我要用信号量使得token跟业务层的request完成分离开,使代码逻辑更清晰。

dispatch_semaphore 介绍

dispatch_semaphore只有三个方法:

1
2
3
4
5
6
//创建信号量
dispatch_semaphore_create
//发送信号量
dispatch_semaphore_signal
//等待信号量
dispatch_semaphore_wait

执行dispatch_semaphore_create 会根据传入的long型参数创建对应数目的信号量;执行 dispatch_semaphore_signal 会增加一个信号量;执行dispatch_semaphore_wait 则会减少一个信号量,如果信号量是0,就会根据传入的等待时间来等待。

这样一解释,不知道你有没有醍醐灌顶。对于上面的需求,我们在请求token的时候创建信号量为0,成功的话发送信号量,在业务层请求前永久等待信号即可。

看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//请求的按钮点击
- (IBAction)buttonPress:(UIButton *)sender
{
//创建信号量
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    [self getToken:semaphore];
//此时的信号量为0,只有token请求成功发送信号量之后,才会往下执行[self request]方法,否则会一直等下去;
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    [self request];
}

- (void)getToken:(dispatch_semaphore_t)semaphore
{
    //以上请求的设置忽略
    NSURLSessionDataTask *task = [mySession dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if (data) {
            NSLog(@"get Token");
//成功拿到token,发送信号量:
            dispatch_semaphore_signal(semaphore);
        }else{
            NSLog(@"token error:%@",error.description);
        } 
    }];
    [task resume];
}

思路理顺了吧~让我们来跑一下程序:
哎呀~卡住了!。。。

是的,因为我们设置了永久等待,所以token请求异步去请求,在当前线程信号量是0,就不会往下执行了,会一直这样卡下去,该怎么解决呢?没错,用异步嘛~

再来代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

- (IBAction)buttonPress:(UIButton *)sender
{
//创建一个并行队列
    dispatch_queue_t queque = dispatch_queue_create("GoyakodCreated", DISPATCH_QUEUE_CONCURRENT);
    //异步执行
    dispatch_async(queque, ^{
        dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
        [self getToken:semaphore];
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        [self request];
    });
    
    NSLog(@"main thread");
}

再跑一下程序,看控制台打印信息:

屏幕快照 2016-08-12 11.12.50.png

如果你还不放心可以多试几次,看看是不是token每次都在request之前。

总结

我们之所以要这样做,其实只有一个目的就是让代码清晰,这样获取Token的方法可以单独提出一个类,判断token是否过期等逻辑就不会跟业务请求混在一起了,毕竟真正项目运用的时候网络请求类要比demo复杂得多。

最后

demo 地址

iOS之SettingsBundle

下图是在手机设置里点击钉钉APP后的页面:

IMG_0663.PNG

IMG_0664.PNG

以上,大家肯定再熟悉不过了,那么它是如何实现的呢?没错,就是今天的主角:Settings.Bundle 。

Settings.Bundle

Settings.Bundle支持六种配置项分别是:Title,MultiValue,Group,Slider,ToggleSwitch,TextField

Title

Title.png

Type:配置类型,默认是Title
Title:配置项显示的Title
Identifier: 配置项的标识符,用来获取配置项的配置内容
Default Value :配置项默认值

MultiValue

MultiValue.png

Type:配置类型,默认Multi Value
Title:配置项显示的标题
Identifier:配置项标识符,用来获取配置项的配置内容
Default:配置项默认值
Titles:显示的标题数组
Values:显示的值数组,与Titles一一对应

Group

Group.png

Type,Title 同上;

TextField

TextField.png

Text Field Is Secure:是否密文显示
KeyBoard Type:键盘类型
Autocapitalization Stylele:自动大写
Autocorrection Style:自动纠正拼写

ToggleSwitch

ToggleSwitch.png

Slider

Slider.png

Minimum Value:最小值
Maximun Value:最大值
Min Value Image Filename:最小值端图片
Max Value Image Filename:最大值端图片

如何设置

1.New File ->Settings.Bundle

AddSettingBundle.png

在右侧的目录会看到:

rootPlist.png

在Root.plist里面添加上面说的配置项目:

plist.png

配置好后,运行项目,然后在设置中点击app,效果如下:

效果图1.png

效果图2.png

程序中获取配置信息

获取配置信息其实很简单,直接上代码:

1
2
3
4
5
6
7
8
9

//获取SettingsBundle信息
    NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
    
    NSLog(@"%@",[userDefaults objectForKey:@"title_perference"]);
    NSLog(@"%@",[userDefaults objectForKey:@"sex_perference"]);
    NSLog(@"%@",[userDefaults objectForKey:@"name_preference"]);
    NSLog(@"%@",[userDefaults objectForKey:@"enabled_preference"]);
    NSLog(@"%@",[userDefaults objectForKey:@"slider_preference"]);

这里有一点需要说明:
如果运行应用后直接直接通过上面的代码获取信息,你会得到null;原因是:
root.plist实际上只是一个静态文件用来在设置里显示;只有当你对它进行修改,它才会往NSUserDefaults里添加。修改配置后,NSUserDefaults的优先级高于root.plist文件。

最后

在企业级应用中,后台访问地址需要用户自行配置的时候,SettingsBundle确实很好用。

Demo_Git地址

Xcode插件

古人有云:工欲善其事,必先利其器;

整理一下自己常用的几个插件,以后会不断更新;要使用插件就不得不说包管理工具:Alcatraz

Alcatraz is an open-source package manager for Xcode 5+. It lets you discover and install plugins, templates and color schemes without the need for manually cloning or copying files. It installs itself as a part of Xcode and it feels like home.

install:

1
curl -fsSL https://raw.github.com/alcatraz/Alcatraz/master/Scripts/install.sh | sh

unstall:

1
rm -rf ~/Library/Application\ Support/Developer/Shared/Xcode/Plug-ins/Alcatraz.xcplugin

VVDocumenter

下载地址

一个帮助你快速添加注释的插件,绝对不容错过。
通过///直接产生注释模板,通过Tab键填充即可,相当好用;完美支持OC & Swift;

效果图.gif

ESJsonFormat

下载地址

一个可以把JSON数据转画为模型属性的插件
使用方法如图:

效果图.gif

XAlign

下载地址

一个帮你自动对齐代码的插件(简直是强迫症福音啊~)
选中要对齐的代码,按下Shift+command+X,搞定~
小Tips:不要选择全部选择一次性对齐,分段对齐更完美,实在对齐不了的就不要强求了

效果图.gif

KSImageNamed

下载地址

一个自动检索出图片名的插件
效果图如下:
效果图.gif

XActivatePowerMode

下载地址

一个用来装逼的插件…写代码也可以很燃..

效果图.gif

ColorSense

下载地址

一个可以直接看到代码设置的RGBA是什么颜色的插件,很好用,墙裂推荐。

效果图.png

最后附上Xcode插件的文件路径:~/Library/Application Support/Developer/Shared/Xcode/Plug-ins

参考文章

iOS之StatusBar详解

随便打开手机上的主流APP,我们不难发现它们的状态栏都是跟导航栏保持一致的背景颜色,如下图的微信和instagram:
WECHAT.PNG
INS.PNG
那么今天我们就来说一下StatusBar这个只有区区20像素高度的小东西。

UIStatusBarStyle

状态栏有两种显示风格:
1.UIStatusBarStyleDefault

default.png

2.UIStatusBarStyleLightContent

lightContent.png

那么它的背景颜色是怎么加上去的呢?很简单,看代码:

1
2
3
4
5
UIView *statusBar = [[UIView alloc] initWithFrame:CGRectMake(0, -20, self.view.frame.size.width, 20)];

statusBar.backgroundColor = myColor;

[self.navigationController.navigationBar addSubview:statusBar];

看完代码就知道没什么可说的了。

要改变状态栏的显示样式(前景颜色)需要在ViewContoller里重载方法:

1
2
3
4
5

- (UIStatusBarStyle)preferredStatusBarStyle
{
    return UIStatusBarStyleLightContent;
}

但是上面这个方法不能直接调用,需要通过下面这个方法来刷新状态栏的样式,例如:

1
2
3
4
5
6
7

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    
    [self setNeedsStatusBarAppearanceUpdate];
}

如果你按照上面说的,在自己的ViewController里面设置一番之后,运行发现你的状态栏还是默认状态…那就对了,因为远没有那么容易。你还需要耐心往下看…

UIViewControllerBasedStatusBarAppearance

info.plist中可以设置状态栏的外观是否是基于视图控制器,键的名称就是UIViewControllerBasedStatusBarAppearance,如果不设置那么它的默认值是YES,表示视图控制器决定了状态栏的风格;如果值设置为NO,则表示每个视图控制器必须显式地使用UIApplication对象来设置状态栏的风格。

哎~这个时候你肯定会疑惑了,我按照你上面说的没有设置plist文件啊,所以默认是YES,那么就是由视图控制器来决定状态栏风格啊,为什么不对呢?

childViewControllerForStatusBarStyle

当我们调用setNeedsStatusBarAppearanceUpdate时,系统会调用application.window.rootViewControllerpreferredStatusBarStyle方法,而不是当前控制器的preferredStatusBarStyle方法。在这个时候,一个重要的方法就要派上用场了,那就是:childViewControllerForStatusBarStyle

childViewControllerForStatusBarStyle默认返回nil。所以我们需要重写这个方法。

假设你的APP里根视图是导航控制器:

1
self.window.rootViewController = [[UINavigationController alloc] initWithRootViewController:viewVontroller];

那么,我们子类化一个导航控制器ZXNavigationController,重写它的childViewControllerForStatusBarStyle方法:

1
2
3
4
5
6
7
8
9
10
@implementation ZXNavigationController

- (UIViewController *)childViewControllerForStatusBarStyle
{
    return self.topViewController;
}

- (void)viewDidLoad {
    [super viewDidLoad];
}

上面代码的意思就是说,不要调用我自己也就是UINavigationController的preferredStatusBarStyle方法,而是去调用navigationController.topViewControllerpreferredStatusBarStyle方法,这样写的话,就能保证当前显示的UIViewController的preferredStatusBarStyle方法能被调用,从而实现statusBar的前景颜色。

然后在application didFinishLaunchingWithOptions:方法里把UINavigationController换成ZXNavigationController :

1
self.window.rootViewController = [[ZXNavigationController alloc] initWithRootViewController:viewVontroller];

Run一下,就会发现:齐活儿了~

如果设置了UIViewControllerBasedStatusBarAppearanceNO;那么就需要显式地通过UIApplication对象来设置状态栏的风格:

1
2
3
4

self.window.rootViewController = [[UINavigationController alloc] initWithRootViewController:viewVontroller];
//setStatusBarStyle从9.0开始不被推荐使用了:
[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent];

最后

说了这么多,简单粗暴地概括下就是:如果想改变StatusBar的显示风格,把UIViewControllerBasedStatusBarAppearance设置为NO,然后通过UIApplication对象设置StatusBar 的 Style。

Demo_Git地址

如何正确使用枚举

枚举类型,对程序员来说再熟悉不过了,但是,你确定你在正确得使用它吗?

在用枚举之前,让我们来区分一下状态选项这两个概念。

状态:同时只能有一种。比如:一个POST请求的返回结果:要么成功,要么失败。这种情况下,成功和失败是POST请求的一种状态。
选项:同时可以有一种或者一种以上。比如:APP可以同时支持竖屏和横屏,这个时候横屏和竖屏就是屏幕方向的两个选项。

那么,我们怎么使用枚举来定义状态和选项呢?

状态

不好的写法(你可能经常见到的写法):

1
2
3
4
5
#define  STATUS_FAIL        0
#define STATUS_SUCCESS 1

//用int变量直接接收
int status = STATUS_SUCCESS;

这样写的不恰当之处在于:
1.宏定义没有类型约束,只是单纯的替换;
2.无法限制状态的所有情况;比如:人为地把status赋值为3,程序可能就会出错找不到对应的状态,因为编译器不会对status=3提示警告。

正确的写法是:

1
2
3
4
5
6
7
typedef enum _ZXSTATUS{
    
    ZXSTATUS_FAIL = 0,
    
    ZXSTATUS_SUCCESS

}ZXSTATUS;

选项

举个例子:

1
2
3
4
5
6
7
8
9
typedef enum _ZXDirection{
    
    ZXDirectionNone = 0,
    ZXDirectionTop = 1 << 0,
    ZXDirectionLeft = 1 << 1,
    ZXDirectionRight = 1 << 2,
    ZXDirectionBottom = 1 << 3,
    
}ZXDirection;

注意:这里的选项是用位运算的方式定义的,这样的好处就是,选项变量可以如下表示:

1
 ZXDirection direction = ZXDirectionBottom | ZXDirectionTop;

所以,用位运算就可以同时支持多个值。

升级

C++ 11之前,我们是不能指定枚举的类型的,但是这之后,我们可以为枚举指定实际的存储类型。Foundation框架已经为我们提供了更加“统一、便捷”的枚举定义方法,我们重新定义上面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
typedef NS_ENUM(NSUInteger,ZXStatus) {
    
    ZXStatusFail = 0,
    ZXStatusSuccess
};

typedef NS_OPTIONS(NSUInteger,ZXDirection) {
    
    ZXDirectionNone = 0,
    ZXDirectionTop = 1 << 0,
    ZXDirectionLeft = 1 << 1,
    ZXDirectionRight = 1 << 2,
    ZXDirectionBottom = 1 << 3,
};

所以,在Objective-C中,最好所有的枚举都用“NS_ENUM”和“NS_OPTIONS”定义,保证统一。

参考文章

3D Touch

苹果在iPhone6s和iPhone6s Plus中加入了3D Touch技术,其实Apple Watch的屏幕上很早就用到这一技术了,并不新颖,由于本人刚买的iPhone6s,所以试玩一下。没有6S和6SP的小伙伴也不要抱怨,好东西来了:SBShortcutMenuSimulator,能让模拟器支持HomeScreen Quick Action。赶紧去尝试吧~

SBShortcutMenuSimulator

1.编译:在终端依次输入下面的命令

1
2
3
4
5
git clone https://github.com/DeskConnect/SBShortcutMenuSimulator.git

cd SBShortcutMenuSimulator

make

2.让SpringBoard支持SBShortcutMenuSimulator

1
2
3
xcrun simctl spawn booted launchctl debug system/com.apple.SpringBoard --environment DYLD_INSERT_LIBRARIES=$PWD/SBShortcutMenuSimulator.dylib

xcrun simctl spawn booted launchctl stop com.apple.SpringBoard

3.预览:

1
echo 'com.apple.mobilecal' | nc 127.0.0.1 8000

注意:’com.apple.mobilecal’ 是系统自带日历的BundleID,记得要换成自己的喔~

Home Screen Quick Action

预览图1.png

创建Quick Action分为静态和动态两种方式:动态就是代码实现,但是程序要运行一次才会有效果;静态就是通过程序的Info.plist文件来设置。

动态实现的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    UIApplicationShortcutIcon *addIcon = [UIApplicationShortcutIcon iconWithType:UIApplicationShortcutIconTypeAdd];
    UIApplicationShortcutIcon *shareIcon = [UIApplicationShortcutIcon iconWithType:UIApplicationShortcutIconTypeShare];
    UIApplicationShortcutIcon *searchIcon = [UIApplicationShortcutIcon iconWithType:UIApplicationShortcutIconTypeSearch];
    
    UIApplicationShortcutItem *item0 = [[UIApplicationShortcutItem alloc] initWithType:@"1" localizedTitle:@"添加" localizedSubtitle:nil icon:addIcon userInfo:nil];
    UIApplicationShortcutItem *item1 = [[UIApplicationShortcutItem alloc] initWithType:@"2" localizedTitle:@"分享" localizedSubtitle:nil icon:shareIcon userInfo:nil];
    UIApplicationShortcutItem *item2 = [[UIApplicationShortcutItem alloc] initWithType:@"3" localizedTitle:@"搜索" localizedSubtitle:nil icon:searchIcon userInfo:nil];
    
    NSArray *itemArray = [NSArray arrayWithObjects:item0,item1,item2, nil];
    
    [[UIApplication sharedApplication] setShortcutItems:itemArray];
    
    return YES;
}

响应回调:

1
2
3
4
5
6
7
8
9
10
- (void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL))completionHandler
{
    if ([shortcutItem.localizedTitle isEqualToString:@"1"]) {
        NSLog(@"添加");
    }else if ([shortcutItem.localizedTitle isEqualToString:@"2"]){
        NSLog(@"分享");
    }else if([shortcutItem.localizedTitle isEqualToString:@"3"]){
        NSLog(@"搜索");
    }
}

特别注意:


1.每个APP最大支持4个Action Item;
2.静态动态同时设置的时候,不会覆盖;

UIKit Peek & Pop

短时间按压屏幕可以实现预览,这就是peek,再用力按压,直接进到另一个页面,就是Pop了。要实现Peek和Pop功能,首先要判断设备是否支持3D Touch功能,然后遵守UIViewControllerPreviewingDelegate,并实现它仅有的两个方法:

delegateMethods.png

监测3D Touch:

我们可以在viewWillAppear方法里检测:

1
2
3
4
5
6
7
8
9
10
11
12
- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    
    if (self.traitCollection.forceTouchCapability == UIForceTouchCapabilityAvailable) {
        NSLog(@"3D Touch 可用");
    }else if (self.traitCollection.forceTouchCapability == UIForceTouchCapabilityUnavailable){
        NSLog(@"3D Touch 不可用");
    }else{
        NSLog(@"3D Touch 未检测");
    }
}

如果程序内修改了3D Touch设置,我们通过traitCollectionDidChange来检测:

1
2
3
4
5
6
7
8
9
10
11
//生命周期内可以通过这个方法重新检测3D Touch功能
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection
{
    if (self.traitCollection.forceTouchCapability == UIForceTouchCapabilityAvailable) {
        //功能可用的情况下进行注册
        [self registerForPreviewingWithDelegate:self sourceView:self.tableView];
        NSLog(@"已注册");
    }else{
        NSLog(@"没注册");
    }
}

实现UIViewControllerPreviewingDelegate的代理方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#pragma mark - viewcontrollerPrewingdelegate
//Peek 代理方法:
- (UIViewController *)previewingContext:(id<UIViewControllerPreviewing>)previewingContext viewControllerForLocation:(CGPoint)location
{
    //防止重复弹出
    if ([self.presentationController isKindOfClass:[PeekViewController class]]) {
        return nil;
    }else{
        //找到正在按压的cell,从而着重显示,
        NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:location];
        UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
        if (!cell) {
            return nil;
        }
        previewingContext.sourceRect = cell.frame;
        PeekViewController *peekVC = [[PeekViewController alloc] initWithNibName:@"PeekViewController" bundle:nil];
        peekVC.preferredContentSize = CGSizeMake(0, 0);
        return peekVC;
    }
}
//Pop 代理方法:
- (void)previewingContext:(id<UIViewControllerPreviewing>)previewingContext commitViewController:(UIViewController *)viewControllerToCommit
{
    PeekViewController *peekVC = [[PeekViewController alloc] initWithNibName:@"PeekViewController" bundle:nil];
    peekVC.sendData = @"from POP";
    [self presentViewController:peekVC animated:YES completion:nil];   
}

你可能见过类似的效果:

peek_quick_actions_2x.png

那么底下的这些选项又是怎么实现的呢?这个呢,需要在目标控制器里设置,也就是说,如果你的A控制器实现了上面的代理方法,然后想跳到B控制器,那么这些选项的设置就要在B控制器里通过UIPreviewAction来设置。

1
2
3
4
5
6
7
8
9
10
11
12
//初始化preViewActionsItems
- (NSArray<id<UIPreviewActionItem>> *)previewActionItems
{
    UIPreviewAction *action0 = [UIPreviewAction actionWithTitle:@"取消关注" style:UIPreviewActionStyleDefault handler:^(UIPreviewAction * _Nonnull action, UIViewController * _Nonnull previewViewController) {
        NSLog(@"取消关注");
    }];
    UIPreviewAction *action1 = [UIPreviewAction actionWithTitle:@"删除" style:UIPreviewActionStyleDestructive handler:^(UIPreviewAction * _Nonnull action, UIViewController * _Nonnull previewViewController) {
        NSLog(@"删除");
    }];
    NSArray *previewActionItemsArray = @[action0,action1];
    return previewActionItemsArray;
}

OK~终于大功告成了。

最后

推荐文章

源码地址