0%

简介

此文用于总结,本人使用过或者收藏过的 Github 第三方类库,以便日后查阅,也便他人借鉴。

资料整理中不定期更新…

开源项目

APP 相关

自动布局

  • Masonry

    最优雅的自动布局解决方案,支持链式编程

  • PureLayout

    又一强大的自动布局解决方案

网络请求

基础工具类以及 Category

  • sstoolkit

    一个不错的工具包,提供各种比如编码、加密、字符串处理等等东西

  • BFKit Swift 版本

    国外的一个大神写的很好用的分类,比较齐全

  • YYKit

    ibireme用心之作

  • BlocksKit

    为基础类提供 Block 支持,很好用

  • DateTools

    非常强大的日期处理工具

  • FlatUIKit

    扩展 Foundation 基本 UI 主件,扁平化风格

  • iOS-Categories

    iOS Objective-C Category, a collection of useful Objective-C Categories extending iOS Frameworks such as Foundation,UIKit,CoreData,QuartzCore,CoreLocation,MapKit Etc.

  • PinYin4Objc

    拼音解析

  • ZipArchive

    解压缩

  • Chameleon

    扁平颜色框架,可以创建非常漂亮的颜色

  • CYLTabBarController

    低耦合集成 TabBarController

  • RKNotificationHub

    在右上方显示通知小角标

  • Material-Controls-For-iOS

    Google 风格的控件

  • YYKit

    A collection of iOS components.

弹框

动画

Gif 引擎

缓存

文本相关

加载进度

抽屉

ScrollView 相关

  • SwipeView

    SwipeView is a class designed to simplify the implementation of horizontal, paged scrolling views on iOS. It is based on a UIScrollView, but adds convenient functionality such as a UITableView-style dataSource/delegate interface for loading views dynamically, and efficient view loading, unloading and recycling.

  • SWTableViewCell

    自定义 Cell 侧滑动作

  • MGSwipeTableCell

    自定义 Cell 侧滑动作,效果多样

  • CHTCollectionViewWaterfallLayout

    瀑布流布局

  • TYSlidePageScrollView

    An easy solution to page views or controllers with header and page tabbar,footer

  • XLPagerTabStrip

    Android PagerTabStrip for iOS. nice

MJ 系列

数据库

  • realm-cocoa

    跨平台轻量级数据库解决方案,励志取代 Core Data & SQLite * fmdb
    sqlite 实现面向对象的封装

图标库

  • PNChart

    提供各种图标样式,性能还非常不错

  • Charts

    Beautiful charts for iOS/tvOS/OSX! The Apple side of the crossplatform MPAndroidChart.

图片处理以及展示相关

日历选择

  • FSCalendar

    iOS7+风格的日历控件,非常漂亮

音频/视频

内购/推广

  • IAPHelper

    in app purchases helper for iOS

  • DAAppsViewController

    应用推广界面,填写合作 app 的 appleId 即可

  • TAPromotee

    交叉推广应用是你可以免费实现的最佳市场推广策略之一。

存储相关

实用控件

辅助类

  • Surge

    利用 Accelerate 高性能数学计算框架

  • KMCGeigerCounter

    显示当前动画的帧率

  • XNGMarkdownParser

    Markdown 解析器,将 Markdown 格式的字符串解析成 NSAttributedString 格式的字符串

  • TransformerKit

    封装了一些常用的 NSValueTransformer

  • RuntimeBrowser

    Objective-C Runtime Browser, for Mac OS X and iOS

  • iOS-Runtime-Headers

    iOS Objective-C headers as derived from runtime introspection

  • appledoc

    Objective-C 代码,苹果风格文档生成器

  • Markingbird

    Markdown processor written in Swift (translation of MarkdownSharp)

Swift

其它

  • FreeCodeCamp

    The https://FreeCodeCamp.com open source codebase and curriculum. Learn to code and help nonprofits.

  • libextobjc

    拓展了一些 Cocoa 类库

  • ParseSourceCodeStudy

    Facebook 开源的 Parse 源码分析【系列】

  • hugo

    使用 Google Go 语言书写的静态博客网站生成器

  • CocoaPods

    Cocoa 包依赖管理工具

  • MDCSwipeToChoose

    实现某 App,左滑不喜欢,右滑喜欢的卡片堆叠功能

  • Dash-Plugin-for-Xcode

    Xcode 的一个插件,整合 Dash ,方便快速查阅文档

  • iOS 开发面试问题

    汇总一些常见的 iOS 面试问题

  • XMPPFramework

    XMPP 即时通信协议框架

  • WebViewJavascriptBridge

    Objective-CJavaScriptUIWebViews / WKWebView 之间交互解决方案

  • articles-1

    Articles for objccn.io. objc.io 的完整、准确、优雅的中文翻译版本

  • articles

    Weekly articles for NSHipster.com

  • ebook

    收集了一些 coding 相关的电子书

  • dev-blog

    nixzhu 的一些 iOS / Web 开发相关的翻译或原创博客文章

  • KVOController

    Simple, modern, thread-safe key-value observing for iOS and OS X.

  • LBXScan

    二维码、扫码、扫一扫、ZXing 和 ios 系统自带扫码封装,扫码界面效果封装

参考链接:

介绍

鉴于现在调试的频繁性和重要性,此文记录了一些基本的 LLDB 调试命令,方便日后查阅。

常用命令 p、po、call、print

1
2
3
4
5
6
7
8
9
10
11
12
p         -- ('expression --')  Evaluate an expression (ObjC++ or Swift) in
the current program context, using user defined variables and
variables currently in scope.
po -- ('expression -O -- ') Evaluate an expression (ObjC++ or Swift)
in the current program context, using user defined variables and
variables currently in scope.
print -- ('expression --') Evaluate an expression (ObjC++ or Swift) in
the current program context, using user defined variables and
variables currently in scope.
call -- ('expression --') Evaluate an expression (ObjC++ or Swift) in
the current program context, using user defined variables and
variables currently in scope.

从官方的描述来看, pprintcall 是一样的,但是 po 就不太一样了,输入一样但是输出不一样。 po 的输出的是具体对象的内容。

如果想要按照特定的格式来打印,如下:

1
2
3
4
5
6
7
8
9
10
(lldb) p/s self.title
(__NSCFString *) $0 = @"目的地"
(lldb) p self.title
(__NSCFString *) $1 = 0x00007fe0c0d4d230 @"目的地"
(lldb) p/x self.title
(__NSCFString *) $2 = 0x00007fe0c0d4d230 @"目的地"
(lldb) p/t self.title
(__NSCFString *) $3 = 0b0000000000000000011111111110000011000000110101001101001000110000 @"目的地"
(lldb) p/a self.title
(__NSCFString *) $4 = 0x00007fe0c0d4d230 -> 0x000000010d2fd2c8 (void *)0x000000010d2fd278: __NSCFString @"目的地"

打印输出格式化

name description
x Regard the bits of the value as an integer, and print the integer in hexadecimal.
d Print as integer in signed decimal.
u Print as integer in unsigned decimal.
o Print as integer in octal.
t Print as integer in binary. The letter ‘t’ stands for “two”.
a Print as an address, both absolute in hexadecimal and as an offset from the nearest preceding symbol. You can use this format used to discover where (in what function) an unknown address is located:(gdb) p/a 0x54320 $3 = 0x54320 <_initialize_vx+396>
c Regard as an integer and print it as a character constant. This prints both the numerical value and its character representation. The character representation is replaced with the octal escape ‘\nnn’ for characters outside the 7-bit ascii range.Without this format, gdb displays char, unsigned char, and signed char data as character constants. Single-byte members of vectors are displayed as integer data.
f Regard the bits of the value as a floating point number and print using typical floating point syntax.
s Regard as a string, if possible. With this format, pointers to single-byte data are displayed as null-terminated strings and arrays of single-byte data are displayed as fixed-length strings. Other values are displayed in their natural types.Without this format, gdb displays pointers to and arrays of char, unsigned char, and signed char as strings. Single-byte members of a vector are displayed as an integer array.
z Like ‘x’ formatting, the value is treated as an integer and printed as hexadecimal, but leading zeros are printed to pad the value to the size of the integer type.
r Print using the ‘raw’ formatting. By default, gdb will use a Python-based pretty-printer, if one is available (see Pretty Printing). This typically results in a higher-level display of the value’s contents. The ‘r’ format bypasses any Python pretty-printer which might exist.

格式: p/x $pc 参考链接:打印输出格式化

lldb 声明变量

1
2
3
(lldb) e NSString *$str = @"http://www.baidu.com"
(lldb) po $str
http://www.baidu.com

我们使用 e 开头声明了变量

调用变量的 API

1
(lldb) po [self.title uppercaseString]

强转返回值类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(lldb) po [self.title characterAtIndex:0]
U+76ee u'目'

(lldb) po (unsigned int)[self.title characterAtIndex:0]
30446

(lldb) po (char)[self.title characterAtIndex:0]
'\xee'

(lldb) po (NSString *)[self.title characterAtIndex:0]
0x00000000000076ee

(lldb) po (unichar)[self.title characterAtIndex:0]
U+76ee u'目'

添加断点

1
2
b 120
b [KLNewDestinationVC setupUI]

b 命令后面加行号,或者指定某个对象调用的方法

设置断点触发条件

右键断点处,在 condition 处编辑,如果设置了条件,只要当条件满足时,才会进入断点,也可以设置条件满足时发出声音和打印提示语。

常用打印视图层次结构

1
(lldb) po [self.view recursiveDescription]

临时刷新界面 UI

1
2
3
(lldb) e ((UIButton *)sender).backgroundColor = [UIColor redColor]
(UICachedDeviceRGBColor *) $41 = 0x00007fdd10715b00
(lldb) e (void)[CATransaction flush]

介绍

chisel facebook 开源的调试框架,封装了很多方便的 LLDB 命令便于调试 iOS App,今天介绍一下使用心得以及一些常用的命令,希望对你有写帮助。

测试环境

Xcode 7.3 and iOS 9.3 simulator.
OS:OSX 10.11.4

pviews and presponder doesn’t work and no output.

Chisel issue on Xcode 7.3 #149

作者正在努力修复中…

Xcode7.2 版本没有问题

安装

brew update
brew install chisel

创建 ~/.lldbinit 假如不存在

编辑内容: command script import /usr/local/opt/chisel/libexec/fblldb.py

查看命令

(lldb)help

查看具体怎样使用

(lldb) help border

常用命令说明

Command Description
pviews Print the recursive view description for the key window.
pvc Print the recursive view controller description for the key window.
visualize Open a UIImage , CGImageRef , UIView , CALayer , NSData (of an image), UIColor , CIColor , or CGColorRef in Preview.app on your Mac.
fv Find a view in the hierarchy whose class name matches the provided regex.
fvc Find a view controller in the hierarchy whose class name matches the provided regex.
show/hide Show or hide the given view or layer. You don’t even have to continue the process to see the changes!
mask/unmask Overlay a view or layer with a transparent rectangle to visualize where it is.
border/unborder Add a border to a view or layer to visualize where it is.
caflush Flush the render server (equivalent to a “repaint” if no animations are in-flight).
bmessage Set a symbolic breakpoint on the method of a class or the method of an instance without worrying which class in the hierarchy actually implements the method.
wivar Set a watchpoint on an instance variable of an object.
presponder Print the responder chain starting from the given object.

Tips:直接点控制台上方第三个按钮呼出 LLDB

pviews(暂不可用)

显示 view 的层级。

border&unborder

border :给view或者layer添加边框颜色和边框的宽度。

使用如下:

(lldb) border 0x7ffc017b2970 -c green -w 10

标示设置 0x7ffc017b2970 视图或者图层一个宽度为 2 的绿色边框。

unborder :去掉view或者layer的边框颜色和边框宽度。

使用如下:

unborder 0x7ffc017b2970

pinternals

这个命令就是打印出来的一个控件( id )类型的内部结构。

使用如下:

pinternals 0x7ffc017b2970

presponder(暂不可用)

打印出一个继承于 UIResponder 控件的消息传递链。

visualize

可以使用 mac 下的预览 app 打开我们的图片 UIImage , CGImageRef 格式的图片,甚至 view 和 layer 的图片。

使用如下:

visualize 0x79ec3140 // 或者变量名,此地址是 id 类型的

pclass

pclass 可以打印出一个对象的继承关系。

使用如下:

pclass 0x7fcd15108800

taplog

这个命令是模拟敲击一下屏幕,并且打印出你敲击屏幕时候事件接收的对象。

hide&show

hide 命令可以直接隐藏一个对象,移除当前遮挡的对象便于你观察后面的对象。show 命令会让它再次显示出来。

bmessage

这个命令就是 LLDB 添加一个断点,譬如 -viewWillAppear: 这个方法,在当前控制器中你没有实现它,但是你又想在调用它的时机触发中断。

这个我就不解释了,需要补充一点的是 Objectiv-c 的方法是带 的。

pvc

打印出当前的控制器层级。

wivar

这个命令是加 watchPoint

参考链接:

Associated Objects 介绍

<objc/runtime.h> 中的函数号称是iOS中最后一把神兵利刃,具有其他方式做不到的,能为应用和框架提供强大功能的能力。但使用不当也可能废掉代码的,一切代码和逻辑都可能被异常糟糕的副作用影响。

就像和魔鬼做交易一样常常让人怀着巨大的恐惧。

历史

对象关联(或称为关联引用)本来是 Objective-C 2.0 运行时的一个特性,起始于 OS X Snow LeopardiOS4

核心函数 3 个 – 允许将任何键值在允许时关联到对象上

  • objc_setAssociatedObject
  • objc_getAssociatedObject
  • objc_removeAssociatedObjects

这有什么用呢?这允许开发者对已经存在的类在扩展中添加自定义的属性

实现的 3 种方式

  • static char
  • selector
  • _cmd_

使用 static char 实现

NSObject+AssociatedObject.h

1
2
3
@interface NSObject (AssociatedObject)
@property (nonatomic, strong) id associatedObject;
@end

NSObject+AssociatedObject.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@implementation NSObject (AssociatedObject)

static char kAssociatedObjectKey;

- (id)associatedObject
{
return objc_getAssociatedObject(self,&kAssociatedObjectKey);
}

- (void)setAssociatedObject:(id)associatedObject
{
objc_setAssociatedObject(self, &kAssociatedObjectKey, associatedObject, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

@end

使用 selector 实现

NSObject+AssociatedObject.h

1
2
3
@interface NSObject (AssociatedObject)
@property (nonatomic, strong) id associatedObject;
@end

NSObject+AssociatedObject.m

1
2
3
4
5
6
7
8
9
10
11
12
13
@implementation NSObject (AssociatedObject)

- (id)associatedObject
{
return objc_getAssociatedObject(self, @selector(associatedObject));
}

- (void)setAssociatedObject:(id)associatedObject
{
objc_setAssociatedObject(self, @selector(associatedObject), associatedObject, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

@end

使用 _cmd_ 实现

NSObject+AssociatedObject.h

1
2
3
@interface NSObject (AssociatedObject)
@property (nonatomic, strong) id associatedObject;
@end

NSObject+AssociatedObject.m

1
2
3
4
5
6
7
8
9
10
11
12
13
@implementation NSObject (AssociatedObject)

- (id)associatedObject
{
return objc_getAssociatedObject(self, _cmd_);
}

- (void)setAssociatedObject:(id)associatedObject
{
objc_setAssociatedObject(self, @selector(associatedObject), associatedObject, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

@end

关联对象的行为

属性根据枚举类型 objc_AssociationPolicy 来决定被关联在对象上的行为:

Behavior 与之等效的@property
OBJC_ASSOCIATION_ASSIGN @property (assign) 或 @property (unsafe_unretained)
OBJC_ASSOCIATION_RETAIN_NONATOMIC @property (nonatomic, strong)
OBJC_ASSOCIATION_COPY_NONATOMIC @property (nonatomic, copy)
OBJC_ASSOCIATION_RETAIN @property (atomic, strong)
OBJC_ASSOCIATION_COPY @property (atomic, copy)

OBJC_ASSOCIATION_ASSIGN 类型关联对象的弱引用不代表weak的弱引用,行为上更像 unsafe_unretained 属性。

对象销毁时间

被关联的对象在声明周期内比对象本身释放要晚很多,在对象调用 -dealloc 中的 object_dispose() 中释放。

删除属性

你或许想要使用 objc_removeAssociatedObjects() 来进行删除操作,但官方文档不建议手动调用这个函数。

这个函数可能会把其他用户对其添加的属性也移除了,正确的方式是调用 objc_setAssociatedObject 方法,传入 nil 值来清除一个关联属性。

优秀样例

  • 添加私有属性用于更好地去实现细节。
  • 添加 public 属性来增强 category 的功能。
  • 创建一个用于 KVO 的关联观察者。

应用举例

UIImagePickerController 图片选择回调

关联一个 block 实现完成选择图片后的回调。

UIImagePickerController+RFBlocks.h

1
2
3
4
5
6
7
8
9
@interface UIImagePickerController (RFBlocks)<UIImagePickerControllerDelegate, UINavigationControllerDelegate>

typedef void(^RFImagePickerFinishBlock)(NSDictionary *info);

@property (nonatomic, copy) RFImagePickerFinishBlock rf_finishBlock;

+ (UIImagePickerController *)rf_imagePickerWithFinishBlock:(RFImagePickerFinishBlock)finishBlock;

@end

UIImagePickerController+RFBlocks.m

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
28
29
30
31
32
33
34
35
36
static const char kFinishBlockKey;

@implementation UIImagePickerController (RFBlocks)

+ (UIImagePickerController *)rf_imagePickerWithFinishBlock:(RFImagePickerFinishBlock)finishBlock
{
UIImagePickerController *picker = [[UIImagePickerController alloc] init];
picker.rf_finishBlock = finishBlock;

return picker;
}

- (RFImagePickerFinishBlock)rf_finishBlock {
return objc_getAssociatedObject(self, &kFinishBlockKey);
}

- (void)setRf_finishBlock:(RFImagePickerFinishBlock)rf_finishBlock {
self.delegate = self;

objc_setAssociatedObject(self, &kFinishBlockKey, rf_finishBlock, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

#pragma mark - UIImagePickerControllerDelegate

- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info
{
RFImagePickerFinishBlock block = self.rf_finishBlock;

if (block) block(info);

[self dismissViewControllerAnimated:YES completion:nil];

self.rf_finishBlock = nil;
}

@end

使用:

1
2
3
4
5
6
7
8
9
__weak typeof(self)weakSelf = self;

UIImagePickerController *picker = [UIImagePickerController rf_imagePickerWithFinishBlock:^(NSDictionary *info) {
NSLog(@"finish picke image\n info:%@",info);

UIImage *img = info[@"UIImagePickerControllerOriginalImage"];
weakSelf.view.layer.contents = (id)img.CGImage;
}];
[self presentViewController:picker animated:YES completion:nil];

或者

1
2
3
4
5
6
7
8
9
10
11
12
13
UIImagePickerController *picker = [[UIImagePickerController alloc] init];

__weak typeof(self)weakSelf = self;

picker.rf_finishBlock = ^(NSDictionary *info) {
NSLog(@"finish picke image\n info:%@",info);

UIImage *img = info[@"UIImagePickerControllerOriginalImage"];
weakSelf.view.layer.contentsGravity = kCAGravityResizeAspect;
weakSelf.view.layer.contents = (id)img.CGImage;
};

[self presentViewController:picker animated:YES completion:nil];

UIButton 按钮事件回调

关联一个按钮事件 block,当触发按钮 UIControlEventTouchUpInside 事件时回调。

UIButton+RFBlcoks.h
1
2
3
4
5
6
7
@interface UIButton (RFBlcoks)

typedef void(^RFButtonClickBlock)(UIButton *button);

@property (nonatomic, copy) RFButtonClickBlock rf_buttonClickBlock;

@end
UIButton+RFBlcoks.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static char kButttonClickBlockKey;

@implementation UIButton (RFBlcoks)

- (RFButtonClickBlock)rf_buttonClickBlock
{
return objc_getAssociatedObject(self, &kButttonClickBlockKey);
}

- (void)setRf_buttonClickBlock:(RFButtonClickBlock)rf_buttonClickBlock
{
objc_setAssociatedObject(self, &kButttonClickBlockKey, rf_buttonClickBlock, OBJC_ASSOCIATION_COPY_NONATOMIC);

[self addTarget:self action:@selector(buttonClicked) forControlEvents:UIControlEventTouchUpInside];
}

- (void)buttonClicked
{
if (self.rf_buttonClickBlock) {
self.rf_buttonClickBlock(self);
}
}

@end

使用:

1
2
3
self.button.rf_buttonClickBlock = ^(UIButton *button){
NSLog(@"%@ clicked",button);
};

Demo 地址:

参考资料:

前言

移动应用风靡的今天,大家越来越觉得,一个 App 的处理事情的效率至关重要,包括一些细节处理,像 本地化localization)和 辅助功能accessibility)的支持是高品质的应用和其它应用区分开的两个特性,今天我们来聊聊一个增加用户体验和效率的功能– 编辑操作

编辑操作

标准编辑动作

处理复制,剪切,删除和粘贴命令

  • 复制 (copy:)
  • 剪切 (cut:)
  • 删除 (delete:)
  • 粘贴 (paste:)

处理选择命令

  • 选择 (select:)
  • 全选 (selectAll:)

富文本编辑命令

  • 加粗 (toggleBoldface:)
  • 斜体 (toggleItalics:)
  • 下划线 (toggleUnderline:)

改变书写方向命令

  • 书写方向左到右 (makeTextWritingDirectionLeftToLeft:)
  • 书写方向右到左 (makeTextWritingDirectionRightToLeft:)

改变文本尺寸命令

  • 变大 (increaseSize:)
  • 变小 (decreaseSize:)

实现 label 的复制粘贴功能

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
28
29
// RFLabel.h
@interface RFLabel : UILabel
@end

// RFLabel.m
@implementation RFLabel

- (BOOL)canBecomeFirstResponder {
return YES;
}

- (BOOL)canPerformAction:(SEL)action
withSender:(id)sender
{
return (action == @selector(copy:) || action == @selector(paste:));
}

#pragma mark - UIResponderStandardEditActions

- (void)copy:(id)sender {
[[UIPasteboard generalPasteboard] setString:self.text];
}

- (void)paste:(id)sender {
NSString *toBePastedString = [[UIPasteboard generalPasteboard] string];
self.text = toBePastedString;
}

@end

在控制器中使用它:

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
28
29
30
31
32
33
34
35
36
37
38
#import "ViewController.h"
#import "RFLabel.h"

@interface ViewController ()

@property (weak, nonatomic) IBOutlet RFLabel *topLabel;
@property (weak, nonatomic) IBOutlet RFLabel *bottomLabel;

@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.

self.topLabel.userInteractionEnabled = YES;
self.bottomLabel.userInteractionEnabled = YES;

UIGestureRecognizer *gestureRecognizer1 = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPressGesture:)];
UIGestureRecognizer *gestureRecognizer2 = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPressGesture:)];

[self.topLabel addGestureRecognizer:gestureRecognizer1];
[self.bottomLabel addGestureRecognizer:gestureRecognizer2];
}

- (void)handleLongPressGesture:(UIGestureRecognizer *)recognizer
{
if (recognizer.state == UIGestureRecognizerStateRecognized) {
[recognizer.view becomeFirstResponder];

UIMenuController *menuController = [UIMenuController sharedMenuController];
[menuController setTargetRect:recognizer.view.frame inView:recognizer.view.superview];
[menuController setMenuVisible:YES animated:YES];
}
}

@end

总结一下,为了能够支持复制粘贴一个 label 的文字,需要完成下面几步:

  • 必须继承 UILabel ,在子类中实现 canBecomeFirstRespondercanPerformAction:withSender: 方法
  • 实现每个可以执行的操作,在方法中和 UIPasteboard 进行交互
  • 初始化 label 时,将 label 的 userInteractionEnabled 属性设置为 YES * 向 label 添加一个手势(或者手动在子类中实现 UIResponder 的方法,例如 touchesBegan:withEvent:
  • 响应手势识别事件,指明 UIMenuController 的位置,设置为可见
  • 最后,使 label 成为第一响应者

UIMenuController

UIMenuController 负责展示编辑动作的菜单项。每个应用都持有自己的一个单例对象 sharedMenuController。
默认情况下,菜单控制器会展示 UIResponderStandardEditActions 这个非正式协议(即不需要对应实现的协议)中的方法,
假如 canPerformAction:withSender: 返回 YES

UIMenuItem

可以通过 UIMenuControllermenuItems 属性添加自定义的命令,每一个都是 UIMenuItem 对象,只需要指定每个动作的 titleaction

1
- (instancetype)initWithTitle:(NSString *)title action:(SEL)action NS_DESIGNATED_INITIALIZER;

UITableView 和 UICollectionView

UITableViewUICollectionView 可以在cell上调出编辑菜单,其实任何自定义的 UIView 都能实现, UIViewController 的子类只是以不同的方式实现而已。为了让 UITableViewUICollectionView 也能实现,你需要使用一些额外的方法否则屏幕上不会显示任何东西。这些方法给予了你更多的控制在显示指定的上下文菜单基于你将要显示在 UITableViewUICollectionView 的内容。

UITableViewDelegate 方法

  • – tableView:shouldShowMenuForRowAtIndexPath:
  • – tableView:canPerformAction:forRowAtIndexPath:withSender:
  • – tableView:performAction:forRowAtIndexPath:withSender:

UICollectionViewDelegate 方法

  • – collectionView:shouldShowMenuForItemAtIndexPath:
  • – collectionView:canPerformAction:forItemAtIndexPath:withSender:
  • – collectionView:performAction:forItemAtIndexPath:withSender:

Bonus

实现 UIImageView 图片的复制粘贴功能

Demo 地址:

参考链接

曾想自定义的 View,可以像系统自带属性一样,并且实时渲染,动态更新内容,现在在 Xcode6 终于让你可以轻松的做到。

你不用编译就能实时预览,现在配置自定义界面方便多了, IBInspectableIBDesignable 使它成为可能。

IBInspectable

IBInspectable 属性提供访问旧功能的新方法:用户自定义的运行时属性。从目前的身份检查器(identity inspector)中访问,这些属性在Interface Builder整合到Xcode。可以通过它来配置Nib,Xib,storyboard实例中的任何键值编码(key-value-coded)属性:

runtime Attributes setting

以前想实现,只能点+号手动添加例如想给 UIView 添加一个圆角半径,设置 Key Path 为:layer.cornerRadius,type 为 Number ,Value 为 4,如果使用 IBInspectable 只需要在 @property 声明后加上 IBInspectable(或者 swift 加上 @IBInspectable ),就可以在 IB 的观察面板中(inspector pannel)里直接编辑。其他交由 Xcode 自动完成,属性名字会自动分组,名称从驼峰(camel-)转换成 title-模式。

inspector pannel

目前可检查属性支持

supported types

备注 :

  1. 经本人亲测,除 NSNumber , NSRange 不会产生观察面板属性, NSRange 不会在 runtime Attributes 中自动添加,其他都会自动生成。

  2. 支持颜色类型 UIColor 但是不支持 CALayerCGColor ,设置颜色时只需设置 UIColor ,不要要设置 CGColor ,系统会自动将 UIColor 转换,例如设置 borderColor。

IBDesignable

当你应用到 UIView 或者 UIView 的子类中的时候,只需加上 IBDesignable ,就可以让 IB 在画布上实时渲染视图。当你更新属性后,视图会自动更新不需要重新运行程序。

标记一个自定义视图为 IBDesignable ,只需要在类名前面加上 IB_DESIGNABLESwift 里加上 @IBDesignable )。你的初始化,布置和绘制方法都将用来在画布上渲染你的自定义视图:

1
2
3
4
5
6
7
8
9
10
11
12
IB_DESIGNABLE

@interface CustomView :UIView {

@property (nonatomic, strong) IBInspectable UIColor *borderColor;
@property (nonatomic, assign) IBInspectable CGFloat borderWidth;

...

}

@end

实时效果:

custom view

有了这个功能,一个设计师或者开放人员可以轻松调整自定义的控件呈现。任何改变,都将立即呈现,有的想 Swift 里面的 playground 功能,实现:所见即所得

由于在 Interface Builder 中呈现自定义视图不会有应用程序的完整上下文,你可能需要生成模拟数据以便显示,例如一个默认用户头像图片或仿制的天气数据。有两种方法可以为这个特殊的上下文添加代码:

  1. prepareForInterfaceBuilder() :此方法与你代码的其余部分一起编译,但只有当视图正在准备在 Interface Builder 显示时执行

  2. TARGET_INTERFACE_BUILDER:#if TARGET_INTERFACE_BUILDER 预处宏,在 Objective-CSwift 下都是工作的,它会视情况编译正确代码

1
2
3
4
5
6
7
8
#if !TARGET_INTERFACE_BUILDE

// this code will run in the app it self

#els

// this code will execute only in IB
#endif

参考资料:

Demo 地址:

输出文件内容到基本输出

cat filename

改变文件的权限

chmod 或
change mode

当前的时间和日期

date

拷贝

copy

显示当前目录下所有文件以及文件夹

ls

显示 dir 文件夹下所有文件以及文件夹

ls dir

查看磁盘空间状况

du

查看当前进程状况

ps

你的用户名和终端类型

who

创建目录

mkdir dir

删除目录

rmdir dir

进入目录

cd dir

删除文件

rm file

显示文件

more file

显示指定文本

echo

改文件名、移动文件

mv source target

显示目录路径命令

pwd

清屏

clear

退出输入命令

Ctrl + c 

改变文件或目录之最后修改时间

touch name

删除目录

 rmdir directory-name 或
 rm -r directory-name
  • rmdir dir1 删除目录 dir1,但 dir1 下必须没有文件存在,否则无法删除。
  • rm -r dir1 删除目录 dir1,及其下所有文件及子目录。

删除文件

rm filename (filename 可为文件名,或文件名缩写符号。)
  • rm file1 删除文件名为 file1 之文件。
  • rm file? 删除文件名中有五个字符,前四个字符为 file 之所有文件。
  • rm f* 删除文件名中,以 f 为字首之所有文件。
  • rm -rf file 删除名为 file 的文件夹及其里面的内容

概述

在 iOS 开发中,从服务器获取数据,然后解析成本地的模型是经常的要做事,而且重复次数特别多,每次增加一个网络请求或者增加一个模型都需要完成 JSONModel 层的转换,手写通过字典 valueForKey: 直接解析已经更不上时代的步伐,学会利用工具提高自己的工作效率,今天就分享一下 Mantle 这个框架解析的心得。

Mantle 解决的痛点:

  • 服务器经常更新(添加或者删除)字段,客服端需要在 Model 层初始化的时候修改取值字段,易出错,而且繁琐。
  • 实现自定义的 Model 的序列化,以便将数据保存到本地,也就是说实现 NSCoding 协议,在模型复制的情况下添加或者修改字段非常麻烦。
  • 自定义的 ModelCopy ,你必须手动实现 NSCopying 协议,而且没有办法反序列化成 JSON

Mantle 很好的解决的以上痛点:

  • 实现了 NSCopying 协议。
  • 实现了 NSCoding 协议,可以通过 NSKeyedArchiver 将数据归档到本地。
  • 提供了 isEqual:hash 的默认实现。
  • 可以在 ModelJSON 之间互相转换。

以一个 CATProfile 模型类为例,演示这个框架怎样以一种非常简单的方式将一个 NSDictionary 对象映射成一个 Objective-C ,反之亦然。

下面的 CATProfile 模型

1
2
3
4
5
6
7
8
9
{
"id": 1,
"name": "Objective Cat",
"birthday": "2013-09-12 13:29:36 +0100",
"website": "http://objc.at",
"location": { "lat": "48.2083", "lon": "16.3731" },
"relationship_status": "single",
"awesome": true
}

下面我们创建一个 MTLModel 子类代表以上的 Json 对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

// CATProfile.h
typedef NS_ENUM(NSInteger, CATRelationshipStatus) {
CATRelationshipStatusSingle = 0,
CATRelationshipStatusInRelationship,
CATRelationshipStatusComplicated
};

@interface CATProfile : MTLModel<MTLJSONSerializing>

@property(strong, nonatomic) NSNumber *profileId;
@property(strong, nonatomic) NSString *name;
@property(strong, nonatomic) NSDate *birthday;
@property(strong, nonatomic) NSURL *website;
@property(nonatomic) CLLocationCoordinate2D locationCoordinate;
@property(nonatomic) CATRelationshipStatus relationshipStatus;
@property(nonatomic, getter=isAwesome) BOOL awesome;

@end

CATProfile 类继承自 MTLModel 并且实现了 MTLJSONSerializing ,协议要求实现 +JSONKeyPathsByPropertyKey 方法

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

// CATProfile.m
@implementation

+ (NSDictionary *)JSONKeyPathsByPropertyKey {
// properties defined in header < : > key in JSON Dictionary
return @{
@"profileId": @"id",
@"websiteURL": @"website",
@"locationCoordinate": @"location",
@"relationshipStatus": @"relationship_status",
};
}

@end

+JSONKeyPathsByPropertyKey 方法返回一个在JSON数据中需要模型的属性匹配的值的字典,这能确保 Mantle 知道那个JSON键键使用来构成一个指定的模型接口。

很明显除了这个列表中的 name , birthdayawesome 属性。假如一个属性在字典中被忽略, Mantle 将自动在 JSON 中查找带有相同名称的接口。

NSValueTransformer

Mantle 还能够处理任意类型的转换,例如 NSStringNSNumber 默认支持。然而,它也需要一些帮助对于处理非任意的类型例如 NSURL枚举 还有自定义的结构体像 CLLocationCoordinate2D

Mantle 依赖Foundation框架下 NSValueTransformer 对象来实现在模型代表的JSON层和OC对象的实际接口之间的值的映射。

创建一个自定义transformer给某个属性,我们需要实现一个叫做 +<propertyName>JSONTransformer 的类方法并且返回一个想要的 NSValueTransformer 对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// mapping birthday to NSDate and vice-versa
+ (NSValueTransformer *)birthdayJSONTransformer {
return [MTLValueTransformer reversibleTransformerWithForwardBlock:^(NSString *dateString) {
return [self.dateFormatter dateFromString:dateString];
} reverseBlock:^(NSDate *date) {
return [self.dateFormatter stringFromDate:date];
}];
}

+ (NSDateFormatter *)dateFormatter {
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
dateFormatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";

return dateFormatter;
}

Mantleruntime 调用这个方法来决定怎样变换 birthday 属性,正向转换block从一个字符串对象到一个 NSDate 对象,反向转换block将 NSDate 对象转换回一个字符串对象,非常棒!

下面列举了一些变换方法,对于所有的我们需要注意的非任意属性以供参考。

NSURL ↔︎ JSON string

1
2
3
+ (NSValueTransformer *)websiteURLJSONTransformer {
return [NSValueTransformer valueTransformerForName:MTLURLValueTransformerName];
}

CLLocationCoordinate2D ↔︎ JSON object

1
2
3
4
5
6
7
8
9
10
+ (NSValueTransformer *)locationCoordinateJSONTransformer {
return [MTLValueTransformer reversibleTransformerWithForwardBlock:^(NSDictionary *coordinateDict) {
CLLocationDegrees latitude = [coordinateDict[@"lat"] doubleValue];
CLLocationDegrees longitude = [coordinateDict[@"lon"] doubleValue];
return [NSValue valueWithMKCoordinate:CLLocationCoordinate2DMake(latitude, longitude)];
} reverseBlock:^(NSValue *coordinateValue) {
CLLocationCoordinate2D coordinate = [coordinateValue MKCoordinateValue];
return @{@"lat": @(coordinate.latitude), @"lon": @(coordinate.longitude)};
}];
}

enum ↔︎ JSON string

1
2
3
4
5
6
7
+ (NSValueTransformer *)relationshipStatusJSONTransformer {
return [NSValueTransformer mtl_valueMappingTransformerWithDictionary:@{
@"single": @(CATRelationshipStatusSingle),
@"relationship": @(CATRelationshipStatusInRelationship),
@"complicated": @(CATRelationshipStatusComplicated)
}];
}

BOOL ↔︎ JSON boolean

1
2
3
+ (NSValueTransformer *)awesomeJSONTransformer {
return [NSValueTransformer valueTransformerForName:MTLBooleanValueTransformerName];
}

从 JSON 创建模型对象

一旦模型配置完成,就该获得 JSON 对象从 API 中,并且把它转换成我们模型的一个实例。首先,我们需要把 JSON 呈现转换成一个 NSDictionary ,它能够用来通过 Mantle 来创建我们的模型,很幸运的是 iOS 提供了一个非常棒的方法来处理它,就是通过 NSJSONSerialization

那样以后, MTLJSONAdapter 类就能利用 Mantle 做繁重的工作来创建我们的模型。

1
2
3
4
5
6
// create NSDictionary from JSON data
NSData JSONData = ... // the JSON response from the API
NSDictionary *JSONDict = [NSJSONSerialization JSONObjectWithData:JSONData options:0 error:NULL];

// create model object from NSDictionary using MTLJSONSerialisation
CATProfile *profile = [MTLJSONAdapter modelOfClass:CATProfile.class fromJSONDictionary:JSONDict error:NULL];

从模型对象创建 JSON

MTLJSONAdapter 也有能力创建一个字典从我们的的模型类中,以便能直接编码回一个JSON字符串。

1
2
3
4
5
6
// create NSDictionary from model class using MTLJSONSerialisation
CATProfile *profile = ...
NSDictionary *profileDict = [MTLJSONAdapter JSONDictionaryFromModel:profile];

// convert NSDictionary to JSON data
NSData *JSONData = [NSJSONSerialization dataWithJSONObject:profileDict options:0 error:NULL];

假如当创建一个 JSON 呈现你的模型是在你的模型中有一个属性不应该包括,你应该返回 NSNull.null ,例如。在 +JSONKeyPathsByPropertyKey 的@{“name” : “NSNull.null”}。 Mantle 将安全的忽略这个属性。

映射数组和字典

大多数情况,模型和其他模型有关系,这些关系普遍通过 JSON 数组或者对象来呈现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
"id": 1,
"name": "Objective Cat",
...,

"owner": {
"id": 99,
"name": "Alexander Schuch"
},
"friends": [
{
"name": "Owly",
"type": "bird"
},
{
"name": "Hedgy",
"type": "mammal"
}
]
}

Mantle 支持映射这些关系到新的模型,为了让 Mantle 知道怎样变换关系,我们可以使用下面提供的分类方法中的一个来返回 NSValueTransformer

1
2
+ (NSValueTransformer *)mtl_JSONDictionaryTransformerWithModelClass:(Class)modelClass;
+ (NSValueTransformer *)mtl_JSONArrayTransformerWithModelClass:(Class)modelClass;

当然 Mantle 需要知道这些官和他们的将要转换的 MTLModel 子类,就像和创建一个新的 MTLModel 子类并且实现将要映射到这些对象的 MTLJSONSerializing 协议一样简单。然后我们可以添加一些新的属性到我们 CATProfile 类中并且实现两个新的转换器。

1
2
3
4
5
6
7
8
9
10
11
12
// CATProfile.h
@property(strong, nonatomic) CATOwner *owner; // CATOwner is a MTLModel subclass
@property(strong, nonatomic) NSArray *friends; // Array of CATFriend objects

// CATProfile.m
+ (NSValueTransformer *)ownerJSONTransformer {
return [NSValueTransformer mtl_JSONDictionaryTransformerWithModelClass:CATOwner.class];
}

+ (NSValueTransformer *)friendsJSONTransformer {
return [NSValueTransformer mtl_JSONArrayTransformerWithModelClass:CATFriend.class];
}

一些有用的补充

我们简单的聊了一下 NSValueTransformer 在前面, NSValueTransformer 有一个非常棒的特征让通过名字全局注册一个变换器成为可能。假如你正在使用相同的变换器在你整个 app 中,确保子类化 NSValueTransformer ,注册你的自定义变换器一次并且在你随后的 MTLModels 中使用它。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// In CATProfile.m
NSString * const kCATCustomValueTransformerName = @"CATCustomValueTransformer";

+ (void)initialize
{
// Register NSValueTransformer
if (self == CATProfile.class) {
CATCustomValueTransformer *transformer = [CATCustomValueTransformer new];
[NSValueTransformer setValueTransformer:transformer forName:kCATCustomValueTransformerName];
}
}

// Then use the custom transformer to translate properties using Mantle
+ (NSValueTransformer *)whateverPropertyJSONTransformer {
return [NSValueTransformer valueTransformerForName:kCATCustomValueTransformerName];
}

结论

Mantle 是一个非常棒的补充处理 JSON APIs,然而,你也必须意识到假如你不得不处理非常复杂的数据或者不稳定的APIs它也是不合适的。

参考资料

介绍 iOS 中以下四种方式实现:

  • CoreImage 中的模糊滤镜
  • UIImage + ImageEffects 的 category 模糊效果
  • iOS8 中 UIVisualEffectView 模糊效果
  • iOS7 以后通过 UIToolBar 实现模糊效果

CoreImage 中的模糊滤镜实现

CoreImage 主要通过 CIFilter 这个类来实现。

这个类支持的滤镜多达 14 类,每个类又细分多款滤镜:

  1. CICategoryBlur
    • CIBoxBlur
    • CIDiscBlur
    • CIGaussianBlur
    • CIMaskedVariableBlur
    • CIMedianFilter
    • CIMotionBlur
    • CINoiseReduction
    • CIZoomBlur
  • CICategoryColorAdjustment
    • CIColorClamp
    • CIColorControls
    • CIColorMatrix
    • CIColorPolynomial
    • CIExposureAdjust
    • CIGammaAdjust
    • CIHueAdjust
    • CILinearToSRGBToneCurve
    • CISRGBToneCurveToLinear
    • CITemperatureAndTint
    • CIToneCurve
    • CIVibrance
    • CIWhitePointAdjust
  • CICategoryColorEffect
  • CICategoryCompositeOperation
  • CICategoryDistortionEffect
  • CICategoryGenerator
  • CICategoryGeometryAdjustment
  • CICategoryGradient
  • CICategoryHalftoneEffect
  • CICategoryReduction
  • CICategorySharpen
  • CICategoryStylize
  • CICategoryTileEffect
  • CICategoryTransition

我们这里使用的是高斯模糊,也就是 CIGaussianBlur

Sample Code:

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
28
29
30
31
32
33
34
35

UIImage *avatar = [UIImage imageNamed:@"avatar"];

/********** CoreImage ************/

// CIImage
CIImage *ciImage = [[CIImage alloc] initWithImage:avatar];

// CIFilter
CIFilter *blurFilter = [CIFilter filterWithName:@"CIGaussianBlur"];

// 设置模糊滤镜半径
[blurFilter setValue:@(20) forKey:@"inputRadius"];

// 将图片输入到滤镜
[blurFilter setValue:ciImage forKey:kCIInputImageKey];

// 将处理好的图片输出
CIImage *outCiImage = [blurFilter valueForKey:kCIOutputImageKey];

// 查询可以设置的参数和一些信息
NSLog(@"%@",[blurFilter attributes]);

// CIContext
CIContext *contex = [CIContext contextWithOptions:nil];

// 获取 CGImage 句柄
CGImageRef outCGImage = [contex createCGImage:outCiImage fromRect:[outCiImage extent]];

// 最终获取到图片
UIImage *blurImage = [UIImage imageWithCGImage:outCGImage];

// 释放 CGImage 句柄
CGImageRelease(outCGImage);

此时 blurImage 图片就是经过滤镜处理后的图片,在放在 ImageView 上加载即可看到效果。

avatar

UIImage + ImageEffects 的 category 模糊效果

使用 Apple 开源的一个图片处理分类来实现,这个使用起来只需一行代码,简洁明了。

1
2
3

UIImage *blurImage = [sourceImage blurImage];

这里封装了一个区域模糊效果的方法

1
2
3

- (UIImage *)blurImageAtFrame:(CGRect)frame;

iOS8 中 UIVisualEffectView 模糊效果

这个效果只支持 iOS8.0 以上的版本,通过 UIVisualEffectView 来实现。

Sample Code:

1
2
3
4
5
6
7
8
9
10

// 1.创建模糊 view
UIVisualEffectView *effectView = [[UIVisualEffectView alloc] initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]];

// 2.设置尺寸
effectView.frame = CGRectMake(0, 100, [UIScreen mainScreen].bounds.size.width, 200);

// 3.添加到 view 当中
[self.view addSubview:effectView];

😊Bonus:
实现 iOS 高版本通知中心炫酷的 Label 模糊效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

// 添加一个文本标签
UILabel *label = [[UILabel alloc] initWithFrame:effectView.bounds];
label.text = @"www.blog.wangruofeng007";
label.textAlignment = NSTextAlignmentCenter;
label.font = [UIFont systemFontOfSize:30];
[effectView.contentView addSubview:label];

/***************** 添加模糊效果 *****************/

// 1.创建子模糊 view
UIVisualEffectView *subEffectView = [[UIVisualEffectView alloc] initWithEffect:[UIVibrancyEffect effectForBlurEffect:(UIBlurEffect *)effectView.effect]];

// 2.设定尺寸
subEffectView.frame = effectView.bounds;

// 3.将子模糊 view 添加到 effectView 的 contenView 才能生效
[effectView.contentView addSubview:subEffectView];

// 4.添加要显示的 view 来达到特殊的效果
[subEffectView.contentView addSubview:label];

效果图:

Demo 地址:

iOS7 以后通过 UIToolBar 实现模糊效果

在 iOS7 以后, UINavigationBarUIToolBar 自带毛玻璃模糊效果,可以通过手动创建 UIToolBar 对象,然后添加到 view 中实现,UIToolBar 区域就可以实现动态毛玻璃模糊效果。

例如在一个状态栏后方添加一个模糊 view,可以在控制器中这样实现,假设没有导航栏

1
2
UIToolbar *statusBar = [[UIToolbar alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 20)];
[self.view addSubview:statusBar];

这个方法非常简单实用😊,快去试试吧。

参考资料: