iOS |知识点整理(9)

延续上一篇iOS |知识点整理(8)

关于关于ALAssetsLibrary assetForURL: always returning nil for photos in “My Photo Stream” in iOS 8.1

  • (void)loadItem:(NSURL *)url withSuccessBlock:(void (^)(void))successBlock andFailureBlock:(void (^)(void))failureBlock {

[library assetForURL:url
resultBlock:^(ALAsset *asset)
{
if (asset){
//////////////////////////////////////////////////////
// SUCCESS POINT #1 – asset is what we are looking for
//////////////////////////////////////////////////////
successBlock();
}
else {
// On iOS 8.1 [library assetForUrl] Photo Streams always returns nil. Try to obtain it in an alternative way

            [library enumerateGroupsWithTypes:ALAssetsGroupPhotoStream
                                   usingBlock:^(ALAssetsGroup *group, BOOL *stop)
             {
                 [group enumerateAssetsWithOptions:NSEnumerationReverse usingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop) {
                     if([result.defaultRepresentation.url isEqual:url])
                     {
                         ///////////////////////////////////////////////////////
                         // SUCCESS POINT #2 - result is what we are looking for
                         ///////////////////////////////////////////////////////
                         successBlock();
                         *stop = YES;
                     }
                 }];
             }

                                 failureBlock:^(NSError *error)
             {
                 NSLog(@"Error: Cannot load asset from photo stream - %@", [error localizedDescription]);
                 failureBlock();

             }];
        }

    }
    failureBlock:^(NSError *error)
    {
        NSLog(@"Error: Cannot load asset - %@", [error localizedDescription]);
        failureBlock();
    }
复制代码

];
}

ref:stackoverflow.com/questions/2…


关于一个内存问题

下面的测试对于NSNumber类型也一样.

@interface AJKTestViewController ()
@property(nonatomic,strong) NSString* str1;
@property(nonatomic,weak) NSString* str2;
@end

@implementation AJKTestViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.str1=[[NSMutableString alloc] initWithString:@"huhu"];
    self.str2=self.str1;
    self.str1=nil;
    NSLog(@"str1:%@  str2:%@",self.str1,self.str2); 输出: str1:null  str2:null


    self.str1=@"huhu";
    self.str2=self.str1;
    self.str1=nil;
    NSLog(@"str1:%@  str2:%@",self.str1,self.str2); 输出: str1:(null)  str2:huhu
    return;
}
复制代码

使用NSSecureCoding协议进行编解码

虽然你不能在一个被NSCoding的文件中存储可执行代码(至少在iOS上不行),但黑客可能会用一个特制的文件欺骗你的应用,来实例化一个你绝对想象不到的类对象,或者在一个你想象不到的环境下实例化类对象。虽然这样做很难带来任何真正的伤害,但这肯定会导致应用崩溃或用户数据丢失。

iOS6中,苹果引入了一个基于NSCoding的新协议,叫做NSSecureCoding。NSSecureCoding和NSCoding几乎完全相同,除了解码的时候你需要指定要解码的对象的key和类,并且如果指定的类和从文件解码到的对象的类不匹配的时候,NSCoder会抛出一个异常来告诉你该数据已经被篡改

@interface Foo : NSObject
@property (nonatomic, strong) NSNumber *property1;
@end

@implementation Foo
//这里得返回YES
+ (BOOL)supportsSecureCoding
{
  return YES;
}

- (id)initWithCoder:(NSCoder *)coder
{
  if ((self = [super init]))
  {
    //security encoding  Decode the property values by key, specifying the expected class
    _property1 = [coder decodeObjectOfClass:[NSNumber class] forKey:@"property1"];
    //非security coding
   // _property1 = [coder decodeObjectForKey:@"property1"];
  }
  return self;
}

- (void)encodeWithCoder:(NSCoder *)coder
{
  // Encode our ivars using string keys as normal
  [coder encodeObject:_property1 forKey:@"property1"];
}

@end
复制代码

关于Chunked encoding

当使用chunked encoding的时候,content-length头就会被忽略,它的尾部是一个空chunk

Data is sent in a series of chunks. The Content-Length header is omitted in this case and at the beginning of each chunk you need to add the length of the current chunk in hexadecimal format, followed by ‘\r\n’ and then the chunk itself, followed by another ‘\r\n’. The terminating chunk is a regular chunk, with the exception that its length is zero. It is followed by the trailer, which consists of a (possibly empty) sequence of entity header fields.

Chunked encoding is useful when larger amounts of data are sent to the client and the total size of the response may not be known until the request has been fully processed. For example, when generating a large HTML table resulting from a database query or when transmitting large images. A chunked response looks like this:

HTTP/1.1 200 OK 
Content-Type: text/plain 
Transfer-Encoding: chunked
7\r\n
Mozilla\r\n 
9\r\n
Developer\r\n
7\r\n
Network\r\n
0\r\n 
\r\n
复制代码

chunk的使用场景:

当生成一个大的HTML table的时候,可以将这个大的HTML table一份一份的传输
The content can be broken up into a number of chunks; each of which is prefixed by its size in bytes. A zero size chunk indicates the end of the response message. If a server is using chunked encoding it must set the Transfer-Encoding header to “chunked”.

Chunked encoding is useful when a large amount of data is being returned to the client and the total size of the response may not be known until the request has been fully processed. An example of this is generating an HTML table of results from a database query. If you wanted to use the Content-Length header you would have to buffer the whole result set before calculating the total content size. However, with chunked encoding you could just write the data one row at a time and write a zero sized chunk when the end of the query was reached.

refLhttps://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Transfer-Encoding

关于HTTP中的Transfer-Encoding

这个特性是用来描述两个端的,与具体的资源无关. 注意它与Content-Encoding的区别
The Transfer-Encoding header specifies the form of encoding used to safely transfer the entity to the user.
Transfer-Encoding is a hop-by-hop header, that is applying to a message between two nodes, not to a resource itself. Each segment of a multi-node connection can use different Transfer-Encoding values. If you want to compress data over the whole connection, use the end-to-end header Content-Encoding header instead.

Transfer-Encoding: chunked
Transfer-Encoding: compress
Transfer-Encoding: deflate
Transfer-Encoding: gzip
Transfer-Encoding: identity
// Several values can be listed, separated by a comma
Transfer-Encoding: gzip, chunked
复制代码

关于HTTP中的if-range

if-range常常用来恢复下载,用来保证本地已经下载的部分文件,没有变化,从而可以继续下载.
if-range后面的值常常是tag或者是时间

The If-Range request HTTP header makes the range request conditional: if the condition is fulfilled, the range request will be and the server send back a 206 Partial Contentanswer with the appropriate body. If the condition is not fulfilled, the full resource is sent back, with a 200 OK status.
This header can be used either with a Last-Modified validator, or with an ETag, but not with both.

The most common use case is to resume a download, to guarantee that the stored resource has not been modified since the last fragment has been received.

Syntax

If-Range: <day-name>, <day> <month> <year> <hour>:<minute>:<second> GMT
If-Range: <etag>
复制代码

ref:developer.mozilla.org/en-US/docs/…

关于HTTP缓存中Expire Header, Last Modified Header or ETags

Expires and Cache-Control are “strong caching headers”

Last-Modified and ETag are “weak caching headers”

First the browser check Expires/Cache-Control to determine whether or not to make a request to the server

If have to make a request, it will send Last-Modified/ETag in the HTTP request. If the Etag value of the document matches that, the server will send a 304 code instead of 200, and no content. The browser will load the contents from its cache.

I recommend using one of the strong caching headers, along with one of the weak caching headers.

也就是说Expires and Cache-Control决定浏览器进不进行第二次请求.
而Last Modified Header or ETags   ,决定第二次返回请求的结果是不是304 ,如果是304则直接使用缓存中的数据
复制代码

ref:

HTTP 缓存
浏览器缓存 Last-Modified / Etag / Expires / Cache-Control 详解
ChenYilong/ParseSourceCodeStudy
复制代码

**关于IOS 中的HTTP缓存 **

1.HTTP协议中,只有GET,有缓存控制
2.值得注意的一点是:
* 如果我们使用的IOS系统的缓存机制的话,那么缓存策略可以为NSURLRequestReloadIgnoringCacheData。它缓存的方法是将查询的参数组成的值作为 key ,对应结果作为value.
* NSURLRequestUseProtocolCachePolicy。一个Request的默认缓存策略。 比较复杂,它大体使用HTTP协议中的缓存策略.
* 如果借助了 Last-Modified 和 ETag,那么缓存策略则必须使用 NSURLRequestReloadIgnoringCacheData 策略,忽略缓存,每次都要向服务端进行校验。
缓存:
复制代码

image.png

    ETag: "50b1c1d4f775c61:df3"
    客户端的查询更新格式是这样的:
    If-None-Match: W/"50b1c1d4f775c61:df3"

服务端与客户端的 ETag 值相等,则 HTTP 状态码为 304,不返回 data。
复制代码

ref:谈谈iOS中的HTTP缓存策略 – Chesterlee’s Zen
ChenYilong/ParseSourceCodeStudy

关于OSAtomicCompareAndSwap32Barrier

OSAtomicCompareAndSwap32Barrier(old, new, addr)

然后这个函数翻译成伪码的话就是这个:

if (*addr == oldvalue) {
    *addr = newvalue;
    return 1;
} else {
    return 0;
} 
复制代码

关于Xib和Storyboard的用法区别

SB:

disadvantage:   
1.团队开发中,同时更改一个文件容易冲突.
2.对SB文件的一个小修改,也会导致整个SB文件的重新编译
3.SB文件变大的时候,打开会比较慢.
4.在SB中的组件不好重用.比如automatic cell.

advantage:
1.程序的流程比较直观
2.提供的automatic cell 以及segue比较好用.
复制代码

Xib:

disadvantage:
1.内容占用会比较高,读取xib涉及到磁盘操作,还有一个xib文件的解析操作.
2.尽量不要在动态table中使用xib.  静态table可以使用xib.
3.xib被加载之后,上面所有的view会加载进内存. 那么有时动态的加载view会更好些.
复制代码

ref:stackoverflow.com/questions/2…
stackoverflow.com/questions/1…

关于MVC和MVVM

1.对于MVC,在 iOS 开发中,系统为我们实现好了公共的视图类:UIView,和控制器类:UIViewController。大多数时候,我们都需要继承这些类来实现我们的程序逻辑,因此,我们几乎逃避不开 MVC 这种设计模式.
MVC 这种分层方式虽然清楚,但是如果使用不当,很可能让大量代码都集中在 Controller 之中,让 MVC 模式变成了 Massive View Controller 模式。

2.Controller 是重用性最差的,因为我们一般不会把冗杂的业务逻辑放在 Model 里面,那就只能放在 Controller 里了.
Controller 测试起来很困难,因为它和 View 耦合的太厉害,要测试它的话就需要频繁的去 mock View 和 View 的生命周期;
而且按照这种架构去写控制器代码的话,业务逻辑的代码也会因为视图布局代码的原因而变得很散乱

3.虽然你可以把控制器里的一些业务逻辑和数据转换的工作交给 Model.因为 View 的主要职责除了显示,就只是讲用户的操作行为交给 Controller 去处理而已。

如何对 ViewController 瘦身?

objc.io 是一个非常有名的 iOS 开发博客,它上面的第一课 《Lighter View Controllers》 上就讲了很多这样的技巧,我们先总结一下它里面的观点:

将 UITableView 的 Data Source 分离到另外一个类中。
将数据获取和转换的逻辑分别到另外一个类中。
将拼装控件的逻辑,分离到另外一个类中
复制代码

1.把网络请求给剥离开

2.新手写代码,喜欢在 Controller 中把一个个 UILabel ,UIButton,UITextField 往 self.view 上用 addSubView 方法放。我建议大家可以用两种办法把这些代码从 Controller 中剥离。

方法一:构造专门的 UIView 的子类,来负责这些控件的拼装。这是最彻底和优雅的方式,不过稍微麻烦一些的是,你需要把这些控件的事件回调先接管,再都一一暴露回 Controller。

方法二:用一个静态的 Util 类,帮助你做 UIView 的拼装工作。这种方式稍微做得不太彻底,但是比较简单。

MVVM的出现主要是为了解决在开发过程中Controller越来越庞大的问题,变得难以维护,所以MVVM把数据加工的任务从Controller中解放了出来,使得Controller只需要专注于数据调配的工作,ViewModel则去负责数据加工并通过通知机制让View响应ViewModel的改变。

MVVM是基于胖Model的架构思路建立的,然后在胖Model中拆出两部分:Model和ViewModel。ViewModel本质上算是Model层(因为是胖Model里面分出来的一部分),所以View并不适合直接持有ViewModel,因为ViewModel有可能并不是只服务于特定的一个View,使用更加松散的绑定关系能够降低ViewModel和View之间的耦合度。

Model层是少不了的了,我们得有东西充当DTO(数据传输对象),当然,用字典也是可以的,编程么,要灵活一些。Model层是比较薄的一层,如果学过Java的小伙伴的话,对JavaBean应该不陌生吧。

ViewModel层,就是View和Model层的粘合剂,他是一个放置用户输入验证逻辑,视图显示逻辑,发起网络请求和其他各种各样的代码的极好的地方。说白了,就是把原来ViewController层的业务逻辑和页面逻辑等剥离出来放到ViewModel层。

View层,就是ViewController层,他的任务就是从ViewModel层获取数据,然后显示。

对 MVVM 的批评主要有两点:

第一点:数据绑定使得 Bug 很难被调试。你看到界面异常了,有可能是你 View 的代码有 Bug,也可能是 Model 的代码有问题。数据绑定使得一个位置的 Bug 被快速传递到别的位置,要定位原始出问题的地方就变得不那么容易了。

第二点:对于过大的项目,数据绑定需要花费更多的内存。

关于ReactiveCocoa

ReactiveCocoa

函数式编程(Functional Programming)和响应式编程(React Programming)也是当前很火的两个概念,它们的结合可以很方便地实现数据的绑定。于是,在 iOS 编程中,ReactiveCocoa 横空出世了,它的概念都非常 新,包括:

函数式编程(Functional Programming),函数也变成一等公民了,可以拥有和对象同样的功能,例如当成参数传递,当作返回值等。看看 Swift 语言带来的众多函数式编程的特性,就你知道这多 Cool 了。
响应式编程(React Programming),原来我们基于事件(Event)的处理方式都弱了,现在是基于输入(在 ReactiveCocoa 里叫 Signal)的处理方式。输入还可以通过函数式编程进行各种 Combine 或 Filter,尽显各种灵活的处理。
无状态(Stateless),状态是函数的魔鬼,无状态使得函数能更好地测试。
不可修改(Immutable),数据都是不可修改的,使得软件逻辑简单,也可以更好地测试。

关于appearance使用时的注意事项

appearance属性的重置,下面,要通过setMessageTextColor方法来改变文字颜色,而不能直接使用_bubbleView.textLabel.textColor = 来设置文字颜色,这样做是不起效果的

 EaseMessageCell *cell = [self appearance];
 cell.messageTextColor = [UIColor blackColor];

 - (void)setMessageTextColor:(UIColor *)messageTextColor
{
    _messageTextColor = messageTextColor;
    if (_bubbleView.textLabel) {
        _bubbleView.textLabel.textColor = _messageTextColor;
    }
}
复制代码

关于

约束中的NSLayoutRelationGreaterThanOrEqual和 NSLayoutRelationLessThanOrEqual是一个范围约束,那么在这个范围约束的基础上,还可以再加其它的约束,作进一步的限制.那么最终的结果是,只要这些约束不冲突,便都会得到范围.

- (void)viewDidLoad {
    [super viewDidLoad];

    _containerView.layer.masksToBounds = YES;
    _containerView.layer.borderWidth = 1.0f;
    _containerView.layer.borderColor = [UIColor redColor].CGColor;

    self.tipLabel.text = @"tutuge.me\niOS";
    [_containerView addSubview:self.tipLabel];
    [_tipLabel sizeToFit];

    [_tipLabel mas_makeConstraints:^(MASConstraintMaker *make) {
        // 设置边界条件约束,保证内容可见,优先级1000
        make.left.greaterThanOrEqualTo(_containerView.mas_left);
        make.right.lessThanOrEqualTo(_containerView.mas_right);
        make.top.greaterThanOrEqualTo(_containerView.mas_top);
        make.bottom.lessThanOrEqualTo(_containerView.mas_bottom);
        _leftConstraint = make.centerX.equalTo(_containerView.mas_left).with.offset(50).priorityHigh(); // 优先级要比边界条件低
        _topConstraint = make.centerY.equalTo(_containerView.mas_top).with.offset(50).priorityHigh(); // 优先级要比边界条件低
//      make.width.mas_equalTo(CGRectGetWidth(_tipLabel.frame) + 8);
//      make.height.mas_equalTo(CGRectGetHeight(_tipLabel.frame) + 4);
    }];

    //这个约束可以保证UILabel的大小为其内容的大小.
    [_tipLabel setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
    [_tipLabel setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];

    UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panWithGesture:)];
    [_containerView addGestureRecognizer:pan];
}

#pragma mark - Pan gesture
- (void)panWithGesture:(UIPanGestureRecognizer *)pan {
    CGPoint touchPoint = [pan locationInView:_containerView];
    _logLabel.text = NSStringFromCGPoint(touchPoint);
    _leftConstraint.offset = touchPoint.x;
    _topConstraint.offset = touchPoint.y;
}
复制代码

下面的约束中,想让viewToAdd尽可能的大:

[self.scrollViewContainer addConstraints:[NSLayoutConstraint 
     constraintsWithVisualFormat:@"H:|-(>=32)-[viewToAdd(<=576)]-(>=32)-|" 
                         options:0 
                         metrics:nil  
                           views:NSDictionaryOfVariableBindings(viewToAdd)
]];
复制代码

上面约束是不能达到这个要求,正确的约束是:

"H:|-(>=32,==32@900)-[viewToAdd(<=576)]-(>=32,==32@900)-|"
复制代码

应该使用弱引用来引用UI组件

综上,无论是引用Xib或者是直接在代码中写UI代码, 都应该使用weak去声明控件,纯代码中如果使用了strong去声明控件,那么有一种情况:如果将控件remove了,那么controller中的view里面的subviews所引用的那条线将会被切断,但是strong属性(指针)所引用的这条线依然存在,由于采用的是强引用,所以控件将不会被ARC给销毁,那么就会一直占用内存,直到控制器销毁.
也就是说,对于UIView组件,应该通过相应的UIView的Api进行操作,关于它的内存,自会有UIView及其父View来进行管理..

处理方法:将属性声明为weak属性,再用临时变量(默认为strong)指向该weak属性

关于Copy和MutableCopy的解析

代码如下:

//对象
@implementation MyObject
- (id)copyWithZone:(nullable NSZone *)zone{
    MyObject* o=[[MyObject alloc] init];
    o.ivar=self.ivar;
    return o;
}
- (id)mutableCopyWithZone:(nullable NSZone *)zone{
    MyObject* o=[[MyObject alloc] init];
    o.ivar=self.ivar;
    return o;
}
@end
//数组操作
        NSMutableArray* arr=[[NSMutableArray alloc] init];
        for(int i=0;i<2;i++){
            [arr addObject:[[MyObject alloc] init]];
        }
        NSLog(@"arr:%@",arr);
        NSMutableArray* arr2=[arr mutableCopy];
        NSLog(@"arr2:%@",arr2);
        NSMutableArray* arr4=[arr copy];
        NSLog(@"arr4:%@",arr4);
        NSMutableArray* arr3=[[NSMutableArray alloc] initWithArray:arr];
//      NSMutableArray* arr3=[[NSMutableArray alloc] initWithArray:arr copyItems:YES];
        NSLog(@"arr3:%@",arr3);
        MyObject* m=arr3.firstObject;
        m.ivar=19;
        NSLog(@"arr3:%@",arr3);

输出:
2016-10-28 11:21:30.060 TESTConstraintWidth[86332:902586] arr:(
    "<MyObject: 0x6080000132f0>",
    "<MyObject: 0x608000013310>"
)
2016-10-28 11:21:30.060 TESTConstraintWidth[86332:902586] arr2:(
    "<MyObject: 0x6080000132f0>",
    "<MyObject: 0x608000013310>"
)
2016-10-28 11:21:30.060 TESTConstraintWidth[86332:902586] arr4:(
    "<MyObject: 0x6080000132f0>",
    "<MyObject: 0x608000013310>"
)
2016-10-28 11:21:30.060 TESTConstraintWidth[86332:902586] arr3:(
    "<MyObject: 0x6080000132f0>",
    "<MyObject: 0x608000013310>"
)
2016-10-28 11:21:40.991 TESTConstraintWidth[86332:902586] arr3:(
    "<MyObject: 0x6080000132f0>",
    "<MyObject: 0x608000013310>"
)
复制代码

1.首先要弄清楚一点的是,这里的MutableCopy是针对数组的,MutableCopy出来的数组,其相关的属性是独立的. 而数组中的

对象是一样的. 也就是说这里的MutableCopy的语义是完整的.

如果想要对数组中的对象,在生成新的数组时,也进行copy操作,那么,就得使用如下方法:

//对数组中的对象也都各自调用一下copy方法.
// NSMutableArray* arr3=[[NSMutableArray alloc] initWithArray:arr copyItems:YES];
复制代码

2.对于容器类的copy和mutableCopy,要区分开容器和容器中的对象.

关于父view的约束

当子view有intrinsicContentSize的时候,父view可以只设置位置方面的约束,而不必设置大小的约束,其大小,会自动计算为适合
其子view内容的大小.

- (void)viewDidLoad {
    [super viewDidLoad];
    UIView* v=[[UIView alloc] init];
    v.backgroundColor=[UIColor redColor];
    v.translatesAutoresizingMaskIntoConstraints=NO;
    [self.view addSubview:v];
   //下面的view我只设置的位置方面的约束
    [self.view addConstraint:[NSLayoutConstraint constraintWithItem:v attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1.0f constant:20]];
    [self.view addConstraint:[NSLayoutConstraint constraintWithItem:v attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0f constant:100]];

//给上面的view添加一个UILabel,那么上面的view会自适应UILabel的大小.
    UILabel* textLabel = [[UILabel alloc] init];
    textLabel.translatesAutoresizingMaskIntoConstraints = NO;
    textLabel.backgroundColor = [UIColor clearColor];
    textLabel.numberOfLines = 0;
    textLabel.textAlignment=NSTextAlignmentCenter;
    textLabel.text=@"kjdflkajsdfljsaf";
    [v addSubview:textLabel];
    NSDictionary* viewDic=@{@"v":textLabel};
    [v addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(==5)-[v]-(==5)-|" options:0 metrics:nil views:viewDic]];
    [v addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(==5)-[v]-(==5)-|" options:0 metrics:nil views:viewDic]];
}
复制代码

098D14E5-86F8-4EAA-A273-54DCD1EA4492.png

具体参见例子工程: TESTConstraintWidth

==================== 另外的一种情况 =============================

- (void)viewDidLoad {
    [super viewDidLoad];
    UIView* v=[[UIView alloc] init];
    v.backgroundColor=[UIColor redColor];
    v.translatesAutoresizingMaskIntoConstraints=NO;
    [self.view addSubview:v];
    //只设置了位置及高度的约束,而宽度由里面的内容确定了
    [self.view addConstraint:[NSLayoutConstraint constraintWithItem:v attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1.0f constant:20]];
    [self.view addConstraint:[NSLayoutConstraint constraintWithItem:v attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0f constant:100]];
    [self.view addConstraint:[NSLayoutConstraint constraintWithItem:v attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0f constant:170]];


    UIView* w=v;
    {
        //注意子view除了设置宽度和高度的约束之外,还要设置相对于父view的贴边约束.这样才能让父view计算出自己的宽度
        UIView* v=[[UIView alloc] init];
        [w addSubview:v];
        v.backgroundColor=[UIColor greenColor];
        v.translatesAutoresizingMaskIntoConstraints=NO;
        [w addConstraint:[NSLayoutConstraint constraintWithItem:v attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:w attribute:NSLayoutAttributeLeft multiplier:1.0f constant:10]];
        [w addConstraint:[NSLayoutConstraint constraintWithItem:v attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:w attribute:NSLayoutAttributeRight multiplier:1.0f constant:-10]];
        [w addConstraint:[NSLayoutConstraint constraintWithItem:v attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:w attribute:NSLayoutAttributeTop multiplier:1.0f constant:10]];
        [v addConstraint:[NSLayoutConstraint constraintWithItem:v attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0f constant:150]];
        [v addConstraint:[NSLayoutConstraint constraintWithItem:v attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0f constant:150]];
    }
}
复制代码

FBFCBC80-FF0B-4B69-B1C1-267E4922D5FD.png

关于范围约束

约束中的NSLayoutRelationGreaterThanOrEqual和 NSLayoutRelationLessThanOrEqual是一个范围约束,那么在这个范围约束的基础上,还可以再加其它的约束,作进一步的限制.那么最终的结果是,只要这些约束不冲突,便都会得到范围.

- (void)viewDidLoad {
    [super viewDidLoad];

    _containerView.layer.masksToBounds = YES;
    _containerView.layer.borderWidth = 1.0f;
    _containerView.layer.borderColor = [UIColor redColor].CGColor;

    self.tipLabel.text = @"tutuge.me\niOS";
    [_containerView addSubview:self.tipLabel];
    [_tipLabel sizeToFit];

    [_tipLabel mas_makeConstraints:^(MASConstraintMaker *make) {
        // 设置边界条件约束,保证内容可见,优先级1000
        make.left.greaterThanOrEqualTo(_containerView.mas_left);
        make.right.lessThanOrEqualTo(_containerView.mas_right);
        make.top.greaterThanOrEqualTo(_containerView.mas_top);
        make.bottom.lessThanOrEqualTo(_containerView.mas_bottom);
        _leftConstraint = make.centerX.equalTo(_containerView.mas_left).with.offset(50).priorityHigh(); // 优先级要比边界条件低
        _topConstraint = make.centerY.equalTo(_containerView.mas_top).with.offset(50).priorityHigh(); // 优先级要比边界条件低
//      make.width.mas_equalTo(CGRectGetWidth(_tipLabel.frame) + 8);
//      make.height.mas_equalTo(CGRectGetHeight(_tipLabel.frame) + 4);
    }];

    //这个约束可以保证UILabel的大小为其内容的大小.
    [_tipLabel setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
    [_tipLabel setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];

    UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panWithGesture:)];
    [_containerView addGestureRecognizer:pan];
}

#pragma mark - Pan gesture
- (void)panWithGesture:(UIPanGestureRecognizer *)pan {
    CGPoint touchPoint = [pan locationInView:_containerView];
    _logLabel.text = NSStringFromCGPoint(touchPoint);
    _leftConstraint.offset = touchPoint.x;
    _topConstraint.offset = touchPoint.y;
}
复制代码

0DB4D662-13E9-4F67-894F-032BBEB5A5F3.png

  1. 下面的约束中,想让viewToAdd尽可能的大:
    [self.scrollViewContainer addConstraints:[NSLayoutConstraint 
         constraintsWithVisualFormat:@"H:|-(>=32)-[viewToAdd(<=576)]-(>=32)-|" 
                             options:0 
                             metrics:nil  
                               views:NSDictionaryOfVariableBindings(viewToAdd)
    ]];
复制代码

上面约束是不能达到这个要求,正确的约束是:

"H:|-(>=32,==32@900)-[viewToAdd(<=576)]-(>=32,==32@900)-|"
复制代码

ref: Using autolayout on iOS how can I specify that a view should take up as much space as possible?


关于swift中的相机权限申请

  • All objects have 750,750 as their Content Compression Resistance Priority.
  • The majority have 250,250 as their Content Hugging Priority.
  • UIImageView and UILabel both have 251,251 as their Content Hugging Priority.
  • UIActivityIndicatorView, UIStepper, and UISwitch have 750,750 as their Content Hugging Priority.
  • UIProgressView has 250,750 as its Content Hugging Priority.

ref: stackoverflow.com/questions/3…

关于swift中的相机权限申请

AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
if(authStatus == AVAuthorizationStatusDenied){
    UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:nil message:@"请到 [设置] 打开 [家庭医-居民] 相机访问权限" delegate:self cancelButtonTitle:@"确定" otherButtonTitles:nil, nil];
    [alertView show];
    return;
}
[[AVAudioSession sharedInstance] requestRecordPermission:^(BOOL granted) {
    if (granted) {

    }
    else {
        UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:nil message:@"请到 [设置] 打开 [家庭医-居民] 麦克风访问权限" delegate:self cancelButtonTitle:@"确定" otherButtonTitles:nil, nil];
        [alertView show];
    }
}];
复制代码

Make sure to:

import AVFoundation
复制代码

The Swift code below checks for all possible permission states:

Swift 3

let cameraMediaType = AVMediaTypeVideo
let cameraAuthorizationStatus = AVCaptureDevice.authorizationStatus(forMediaType: cameraMediaType)

switch cameraAuthorizationStatus {
case .denied: break
case .authorized: break
case .restricted: break

case .notDetermined:
    // Prompting user for the permission to use the camera.
    AVCaptureDevice.requestAccess(forMediaType: cameraMediaType) { granted in
        if granted {
            print("Granted access to \(cameraMediaType)")
        } else {
            print("Denied access to \(cameraMediaType)")
        }
    }
}
复制代码

Swift 2

let cameraMediaType = AVMediaTypeVideo
let cameraAuthorizationStatus = AVCaptureDevice.authorizationStatusForMediaType(cameraMediaType)

switch cameraAuthorizationStatus {
case .Denied: break
case .Authorized: break
case .Restricted: break

case .NotDetermined:
    // Prompting user for the permission to use the camera.
    AVCaptureDevice.requestAccessForMediaType(cameraMediaType) { granted in
        if granted {
            print("Granted access to \(cameraMediaType)")
        } else {
            print("Denied access to \(cameraMediaType)")
        }
    }
}
复制代码

As an interesting side note, did you know that iOS kills the app if it’s running while you change its camera permissions in Settings? See this developer forum discussion (needs registration).

The system actually kills your app if the user toggles your app’s access to camera in Settings. The same applies to any protected dataclass in the Settings->Privacy section.

ref:stackoverflow.com/questions/2…


关于action sheet的呈现方式

UIActionSheet *actionSheet = [[UIActionSheet alloc]
                                  initWithTitle:@"请选择二维码类型:"
                                  delegate:self
                                  cancelButtonTitle:@"取消"
                                  destructiveButtonTitle:nil
                                  otherButtonTitles:@"签约居民", @"会诊转诊",@"体检预约",nil];
    actionSheet.actionSheetStyle = UIBarStyleDefault;
    [actionSheet showFromTabBar:self.tabBarController.tabBar];
复制代码

关于NSProgress的使用

1.关于NSProgress的注意事项:

NSProgress对象是线程相关的对象,当调用[progress becomeCurrentWithPendingUnitCount:1],那这progress就成为当前线程的当前progress对象,
那么当我们在当前线程,再建立一个NSProgress对象progress1的时候,那么progress1自动成为progress的子progress.

[progress becomeCurrentWithPendingUnitCount:1]
//生成另外的一progress,来完成任务
[progress resignCurrent]

当调用[progress resignCurrent],那么上面pending的unit cound标记为完成
复制代码

2.当我们要把任务分配给不同线程完成时,这时要调用,NSProgress的addChild方法,这个方法是IOS9+有效.

//多任务报告进度

- (void)viewDidLoad {
    [super viewDidLoad];
    NSProgress* progress=[NSProgress progressWithTotalUnitCount:-1];
    self.progress.observedProgress=progress;
    [progress becomeCurrentWithPendingUnitCount:100];
    [self doWork:^{
        NSLog(@"work done");
    } progress:progress];
    [progress resignCurrent];
}

-(void)doWork:(void(^)(void))handler progress:(NSProgress*)mainProgress{
    NSProgress* childProgress=[NSProgress progressWithTotalUnitCount:-1];
    NSProgress* childProgress1=[NSProgress progressWithTotalUnitCount:-1];
    mainProgress.totalUnitCount=100;
    childProgress.totalUnitCount=50;
    childProgress1.totalUnitCount=50;
    [mainProgress addChild:childProgress1 withPendingUnitCount:50];
    [mainProgress addChild:childProgress withPendingUnitCount:50];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
        for(int i=0;i<100;i++){
            [NSThread sleepForTimeInterval:0.05];
            NSLog(@"progress%d",i);
            childProgress.completedUnitCount=(i+1)/(100.0)*50;
        }
    });
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
        for(int i=0;i<100;i++){
            [NSThread sleepForTimeInterval:0.09];
            NSLog(@"progress1:%d",i);
            childProgress1.completedUnitCount=(i+1)/(100.0)*50;
        }
    });
}
复制代码

//=============================================================================

- (void)viewDidLoad {
    [super viewDidLoad];
    NSProgress* progress=[NSProgress progressWithTotalUnitCount:-1];
    self.progress.observedProgress=progress;
    //将progress设置成当前线程的current progress
    [progress becomeCurrentWithPendingUnitCount:1];
    [self doWork:^{
        NSLog(@"work done");
    }];
    [progress resignCurrent];
}

-(void)doWork:(void(^)(void))handler{
    NSProgress* mainProgress=[NSProgress currentProgress];
    //注意child progress和main Progress得在同一个线程中.
    NSProgress* childProgress=[NSProgress progressWithTotalUnitCount:-1];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
        //初始化完成之后,更新progress的属性可以在不同的线程中了.
        mainProgress.totalUnitCount=1;
        childProgress.totalUnitCount=50;
        for(int i=0;i<100;i++){
            [NSThread sleepForTimeInterval:0.05];
            childProgress.completedUnitCount=(i+1)/(100.0)*50;
        }
        handler();
    });
}
复制代码

NSProgress – Ole Begemann
www.tuicool.com/articles/7z…


关于离屏渲染

//提高效率,防止离屏渲染
self.layer.cornerRadius = 6;
self.layer.masksToBounds = YES; // 裁剪
self.layer.shouldRasterize = YES; // 缓存
self.layer.rasterizationScale = [UIScreen mainScreen].scale;
复制代码

优化性能时,可能会开启CALayer.shouldRasterize 来缓存绘制比较慢的层(例如圆角),但在retina屏幕上会引起图片不清晰,需要设置self.layer.rasterizationScale = [UIScreen mainScreen].scale;


关于layout subview bounds的设置

-(void)layoutSubviews{
    [super layoutSubviews];
    if(_cview==nil){
        _cview=[[UIView alloc] init];
        [self addSubview:_cview];
        //下面的做法,大多时候是正确的,但是有时,客户端可能是手动调用了 setNeedsLayout,layoutIfNeed,导致下面的self.bounds不是最终呈现的bounds,就会出现问题. 那么解决方案,就是把下面的代码,放到if语句块的外面.
        CGRect r=self.bounds;
        r.origin.y=31;
        r.size.height=r.size.height-r.origin.y;
        _cview.frame=r;
    }
}
复制代码

关于isEqual的默认实现

The default NSObject implementation of isEqual: simply checks for pointer equality.

The answer about default implementation of isEqual: is comprehensive one. So I just add my note about default implementation of hash. Here it is:

-(unsigned)hash {return (unsigned)self;}
复制代码

I.e it’s just the same pointer value which is used in isEqual:. Here’s how you can check this out:

NSObject *obj = [[NSObject alloc] init];
NSLog(@"obj: %@",obj);
NSLog(@"hash: %lx",obj.hash);  
复制代码

输出:

obj: <NSObject: 0x7fcfb2f257c0>
hash: 7fcfb2f257c0
复制代码

UIScrollView代理方法的讲解

UIScrollView的各代理方法主要分为两种,一种是用户拖动相关的,一种是滑动减速相关的

//只要它的contentOffset发生变化,就调用.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
//    NSLog(@"%s", __PRETTY_FUNCTION__);
}

//用户刚一开始拖动的时候,调用这个方法
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
    NSLog(@"%s", __PRETTY_FUNCTION__);
    [self logDraggingAndDecelerating];
}
复制代码

////该方法从 iOS 5 引入,在 didEndDragging 前被调用,当 willEndDragging 方法中 velocity 为 CGPointZero(结束拖动时两个方向都没有速度)时,didEndDragging 中的 decelerate 为 NO,即没有减速过程,willBeginDecelerating 和 didEndDecelerating 也就不会被调用。反之,当 velocity 不为 CGPointZero 时,scroll view 会以 velocity 为初速度,减速直到 targetContentOffset。值得注意的是,这里的 targetContentOffset 是个指针,没错,你可以改变减速运动的目的地,这在一些效果的实现时十分有用,

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
{
    NSLog(@"%s velocity: %@, targetContentOffset: %@", __PRETTY_FUNCTION__,
          [NSValue valueWithCGPoint:velocity],
          [NSValue valueWithCGPoint:*targetContentOffset]);
    [self logDraggingAndDecelerating];
}

//用户拖动结束的时候,调用这个方法,如果decelerate为零,那么滚动会立即停止.
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
    NSLog(@"%s --> willDecelerate:%d", __PRETTY_FUNCTION__,decelerate);
    [self logDraggingAndDecelerating];
}

//减速动画开始前被调用。
- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView
{
    NSLog(@"%s", __PRETTY_FUNCTION__);
    [self logDraggingAndDecelerating];
}

//减速动画结束时被调用,这里有一种特殊情况:当一次减速动画尚未结束的时候再次 drag scroll view,didEndDecelerating 不会被调用,并且这时 scroll view 的 dragging 和 decelerating 属性都是 YES。新的 dragging 如果有加速度,那么 willBeginDecelerating 会再一次被调用,然后才是 didEndDecelerating;如果没有加速度,虽然 willBeginDecelerating 不会被调用,但前一次留下的 didEndDecelerating 会被调用,
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
    NSLog(@"%s", __PRETTY_FUNCTION__);
    [self logDraggingAndDecelerating];
}
复制代码
  1. 当连续滚动UIScrollView的时候,各代理的调用顺序可能如下,注意下面各代理的调用顺序,

先是拖动相关的,然后再说减速相关的.

scrollViewWillBeginDragging: 
scrollViewWillEndDragging: withVelocity: targetContentOffset: 
scrollViewDidEndDragging: willDecelerate: 
scrollViewWillBeginDecelerating: 
scrollViewWillBeginDragging: 
scrollViewWillEndDragging: withVelocity: targetContentOffset: 
scrollViewDidEndDragging: willDecelerate: 
scrollViewWillBeginDecelerating: 
...
scrollViewWillBeginDragging: 
scrollViewWillEndDragging: withVelocity: targetContentOffset: 
scrollViewDidEndDragging: willDecelerate: 
scrollViewWillBeginDecelerating: 
scrollViewDidEndDecelerating:
复制代码
  1. 刚开始拖动的时候,dragging 为 YES,decelerating 为 NO;decelerate 过程中,dragging 和 decelerating 都为 YES;decelerate 未结束时开始下一次拖动,dragging 和 decelerating 依然都为 YES。所以无法简单通过 table view 的 dragging 和 decelerating 判断是在用户拖动还是减速过程。

解决这个问题很简单,添加一个变量如 userDragging,在 willBeginDragging 中设为 YES,didEndDragging 中设为 NO。

二.什么时UIScrollView的滚动停止:

It’s a common requirement for an application to know when a user stops scrolling aUIScrollView.
There is the obvious delegate, scrollViewDidEndDecelerating, method that announces when a scroll view stops moving. However, this will only fire if the UIScrollView diddecelerate to a stop. It won’t fire if a user carefully scrolls to a spot without creating any momentum.
To cover all the bases, you need to also listen to scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate for instances where the user stopped dragging and the UIScrollView also stopped without accelerating.

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{  //在没有减速情况下的停止
  if (!decelerate)
    [self postScrollExplosion:scrollView];
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{ //在减速情况下的停止
  [self postScrollExplosion:scrollView];
}
复制代码

If will decelerate argument is false, then we know the UIScrollView stopped scrolling.


在interface builder的outlets到底应该是strong还是weak

一般来说,只有interface builder中top level object才需要strong
既然top level object已经是strong了,那其中的子view,作为outlets,做为weak引用即可

The current recommended best practice from Apple is for IBOutlets to be strong unless weak is specifically needed to avoid a retain cycle. As Johannes mentioned above, this was commented on in the “Implementing UI Designs in Interface Builder” session from WWDC 2015 where an Apple Engineer said:
And the last option I want to point out is the storage type, which can either be strong or weak. In general you should make your outlet strong, especially if you are connecting an outlet to a subview or to a constraint that’s not always going to be retained by the view hierarchy. The only time you really need to make an outlet weak is if you have a custom view that references something back up the view hierarchy and in general that’s not recommended.
I asked about this on Twitter to an engineer on the IB team and he confirmed that strong should be the default and that the developer docs are being updated.

twitter.com/_danielhall…

Summarized from the developer library:
From a practical perspective, in iOS and OS X outlets should be defined as declared properties. Outlets should generally be weak, except for those from File’s Owner to top-level objects in a nib file (or, in iOS, a storyboard scene) which should be strong. Outlets that you create will therefore typically be weak by default, because:

  • Outlets that you create to, for example, subviews of a view controller’s view or a window controller’s window, are arbitrary references between objects that do not imply ownership.

  • The strong outlets are frequently specified by framework classes (for example, UIViewController’s view outlet, or NSWindowController’s window outlet).

      @property (weak) IBOutlet MyView *viewContainerSubview;
      @property (strong) IBOutlet MyOtherClass *topLevelObject;
    复制代码

stackoverflow.com/questions/7…


宏展开中的换行

#define AA(x) #x
@interface AppDelegate ()
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    NSString* s= @AA(dfa
                     sdf
                     kds
                     fsf);
   //s经宏替换后,变成  @"dfa sdf kdsfsf"
}
复制代码

UITextView中text的KVO

因为在UITextView内部,当你输入文本的时候,内部并不是调用setText,而是通过计算直接得出的. 所以当你在UITextView输入文字的时,下面的监听并不会有效.
但是当你调用[UITextView setText:]方法的时候,下面的KVO是可以监听到的.

78BE31F3-BF1B-47F6-8AF3-D724A19DF398.png


关于status bar字体及背景颜色

UIViewControllerBasedStatusBarAppearance的实际作用如下:
这个属性只影响如何设置status bar上字体的颜色是黑色还是白色,对status bar的背景色无影响。
status bar的背景色在iOS7上永远是透明的。因此status bar的背景色为最上面那层view的颜色,当有navigation bar时,为navigation bar的背景颜色.当

UIViewControllerBasedStatusBarAppearance = NO时:
UIApplication 的setStatusBarStyle方法生效:
[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent];
viewController的preferredStatusBarStyle方法无效:
- (UIStatusBarStyle)preferredStatusBarStyle{
    return UIStatusBarStyleLightContent;
}

UIViewControllerBasedStatusBarAppearance = YES时:
UIApplication 的setStatusBarStyle无效。
UINavigationviewController的preferredStatusBarStyle方法有效。 准确的说,应该是第一个加载的viewController的preferredStatusBarStyle的方法有效
复制代码

IOS7以后,window是扩展到整个屏幕的,status bar则是悬浮在最上面.

When a UINavigationController is present, the UINavigationBar will automatically be placed below the status bar (the standard 20 point offset from the top of the screen that we are accustomed to from previous iOS versions). The background of the status bar will be modified to match the UINavigationBar below it. `The status bar will inherit the color and transparency of the UINavigationBar.Additionally


阻止UISearchDisplayController全屏

如果当前VC中有NavigationBar的话,那么UISearchDisplayController会自动隐藏UINavigationBar,并自动全屏.

//在UISearchDisplayController的子类中重写如下方法
- (void)setActive:(BOOL)visible animated:(BOOL)animated;
{
    if(self.active == visible) return;
    [self.searchContentsController.navigationController setNavigationBarHidden:YES animated:NO];
    [super setActive:visible animated:animated];
    [self.searchContentsController.navigationController setNavigationBarHidden:NO animated:NO];
    if (visible) {
        [self.searchBar becomeFirstResponder];
    } else {
        [self.searchBar resignFirstResponder];
    }
}
复制代码

使用instruments查看retain cycle

在allocation中,查看属于自己工程的对象,然后反复操作,看看有没有应该被释放,而没有释放的对象.

I just transitioned an older app to use ARC. Instruments showed no leaks, but the allocations continued to go up. I found that by looking at the live objects for something that I knew should be deleted, I was able to track down the retains without a release. Here are the basic steps:

  1. Use the Allocations tool in Instruments
  2. Play with your app for a while (if you know what isn’t being released, this goes faster)
  3. Change Statistics to Objects in the Allocations jump bar
  4. Sort by Category and find the class name of your unreleased object
  5. Find a living instance and click the little right arrow next to the memory address
  6. Now you can see the history of retains and releases for an object

9ECFE7F2-00E3-4B12-90C2-6531718A561F.png

stackoverflow.com/questions/9…


关于NSTextAttachment的默认位置

NSTextAttachment * textAttachment = [[ NSTextAttachment alloc ] initWithData:nil ofType:nil ] ;
UIImage * image = [ UIImage imageNamed:@"a.jpg" ];  //my emoticon image named a.jpg
textAttachment.image = image ;
textAttachment.bounds = CGRectMake(0,0, 8,8);
复制代码

显示如下:extAttachment显示的origin point,在baseline上面.

F3BEB695-20DF-4011-9552-6A16C3A9DA72.png

对比下面的图可知:

如果想让图片居中的话,得进行如下的调整:

A887EB16-CC9A-4892-A294-AC3015D0A405.png

 NSTextAttachment * textAttachment = [[ NSTextAttachment alloc ] initWithData:nil ofType:nil ] ;
UIImage * image = [ UIImage imageNamed:@"a.jpg" ];  //my emoticon image named a.jpg
textAttachment.image = image ;
UIFont* font=[UIFont boldSystemFontOfSize:40];
textAttachment.bounds = CGRectMake(0, (mid-10/2.0)/2.0f, 10,10);
复制代码

4F27F45E-CECA-4F52-9D72-3CABA8482BFC.png


关于NSKeyedArchiver的规则

  1. mydata=[NSKeyedArchiver archivedDataWithRootObject:thing1];//假设thing1实现了NSCoding协议
  2. thing1=[NSKeyedUnarchiver unarchivedObjectWithData:mydata];

What happens is simple: you use the archivedDataWithRootObject() method of NSKeyedArchiver, which turns an object graph into an NSData object, then write that to NSUserDefaults as if it were any other object. If you were wondering, “object graph” means “your object, plus any objects it refers to, plus any objects those objects refer to, and so on.”

The rules are very simple:

  1. All your data types must be one of the following: boolean, integer, float, double, string, array, dictionary, NSDate, or a class that fits rule 2.
  2. If your data type is a class, it must conform to the NSCoding protocol, which is used for archiving object graphs.
  3. If your data type is an array or dictionary, all the keys and values must match rule 1 or rule 2.

关于weakify和strongify的用法

@weakify(self)
[[self.searchText.rac_textSignal
  map:^id(NSString *text) {
      return [UIColor yellowColor];
  }]
 subscribeNext:^(UIColor *color) {
     @strongify(self)
     self.searchText.backgroundColor = color;
 }];
复制代码

code after preprocess:

@autoreleasepool {} __attribute__((objc_ownership(weak))) __typeof__(self) self_weak_ = (self);
    [[self.searchText.rac_textSignal
      map:^id(NSString *text) {
          return [UIColor yellowColor];
      }]
     subscribeNext:^(UIColor *color) {
         @try {} @finally {}
 __attribute__((objc_ownership(strong))) __typeof__(self) self = self_weak_; // 1
 self.searchText.backgroundColor = color;  //2
     }];

1: define a new local variable “self”. this will shadow the global one.
2: so here we used the local variable “self”--self_weak_.
tips:
1.if we used self.xxx in block, we should place @strongify(self) over it.
2.don't forget use @weakify(self) to define the variable self_weak_.
复制代码

(PS: I’m trying to learn English. I hope that you can understand what I’m saying.)

ref: stackoverflow.com/questions/2…


对UIButton添加不同的回调

- (void)viewDidLoad {
    [super viewDidLoad];
     [_btn addTarget:self action:@selector(addBtnClicked) forControlEvents:UIControlEventTouchUpInside];
     [_btn addTarget:self action:@selector(addBtnClicked1) forControlEvents:UIControlEventTouchUpInside];
}

-(void)addBtnClicked{
    NSLog(@"addBtnClicked");
}

-(void)addBtnClicked1{
    NSLog(@"addBtnClicked");
}
复制代码

UUID方案

82B26739-3D15-47F3-ACBD-7223EC739DD6.png


关于UITableViewCell dynamic height

// 计算UILabel的preferredMaxLayoutWidth值,多行时必须设置这个值,否则系统无法决定Label的宽度
label.preferredMaxWidth = [UIScreen mainScreen].bounds.size.width - margin - padding;
手动计算宽度,感觉回到了没有Autolayout的时代=。=
复制代码

关于UITableViewCell中使用UITextField

在UITableViewCell中使用UITextField的时候,在提交的时候,要注意设置[self.view endEditing:YES]否则可能取不到最新值


关于UITableViewCell中自定义view的用法

  1. 当第一次被显示,并加载数据,这时,应该如何显示,注意这时layoutSubview将被调用

  2. 当cell被复用的时候. 也就是说,上面的创建的cell,被复用时,再次加载不同或相同的数据,那么该如何显示

  3. 就是cell高度的管理,如果cell的高度要求动态变化,那么要在数据发生变化的时候,及时反馈reloadData. 一般来说,高度发生变化的时候,把这个高度存放一个字典中,以IndexPath做为key,然后reloadData.

  4. NSIndexPath不能直接用做Key,要把它转换成字符串之后,才能用作key


关于UITextView

1.UITextView的高度计算计算如下:
textView.contentSize.height+textView.textContainerInset.top+_textView.textContainerInset.bottom.
textView.textContainerInset的值一般如下:[8 0 8 0]

2.UITextView在内容变化以后,会更新它的contentOffset.

3.UITextView在keyboard dismiss之后,也会更新它的contentOffset. 一般来说会让里面的content滚动到顶部. 调用栈如下:
layoutSubview -> updateContentSize -> setContentOffset

4.当设置UITextView的scrollEnabled为NO的时候,对其进行frame的改变,可能会不准确. 这时如果允许的话,可以设置UITextView的userInterfaceEnabled为NO.来禁止滚动

5.在UITableViewCell中使用UITextView,如果让它高度和键盘自适应,这时,可能会有些困难. 最好在一个新的页面的编辑.


关于hash

一般在重写isEqual:的方法的时候,

一个对象在用作key值时,其 hash 方法会被调用,用以生成一个唯一标识符,NSDictionary 需要根据唯一 key 值(根据 hash 算法生成的值)查找对象, NSSet 需要根据 hash 值来确保过滤掉重复的对象。

用作 NSMutableDictionary 中的 key 时,hash 方法执行了,不过崩溃了,因为 Model 类没有实现 
NSCopying 协议
在创建不可变数组时,model 作为 key 会执行 hash 方法,但同样会崩溃,因为 Model 类没有实现 NSCopying 协议
    Model        *model      = [Model new];
    NSDictionary *dictionary = @{model : @"A"};
    dictionary = nil;
字典在执行setObject:forKey:方法的时候,会先执行这个key值对象的hash方法用以生成一个键值,然后再copy这个key值对象。
set的 addObject:方法执行的流程如下:先获取对象modelA的hash值,再添加modelB的时候获取modelB的hash值,然后进行比较,如果两者的比较结果一致,则认为这是相同的对象。
复制代码

关于URL Schemes

在一个app中进行如下设置:

53BA6FE1-4135-4DDD-9792-BB9D53A61830.png

在另外一个app中,运行如下代码,则会打开相应的上面的app:

 [[UIApplication sharedApplication] openURL:[NSURL RLWithString:@"cloud://me.me.venj.myapp-ios"]];
复制代码

在Mac和iOS中注册自定义的URL Scheme – Cocoa学习


关于IOS正则表达式中的转义

今天帮朋友弄这个问题,json返回全都是带”的,于是要去掉这个反斜杠,但是OC里面的”是转义符,不能直接用@””之类的表示,一顿搜索之后,找到了OC对转义字符的表示方法,如下:

\a - Sound alert
\b - 退格
\f - Form feed
\n - 换行
\r - 回车
\t - 水平制表符
\v - 垂直制表符
\\ - 反斜杠
\" - 双引号
\' - 单引号
复制代码

所以在IOS的正则表达式里,\w代表字母,但是在书写的时候,要写成”\w”,总而言之不能直接使用,因为它和其后的字符代表着转义. 正则中使用的\字符,所以要使用\
[]有特殊含义,要使用其字面意思,就得写成”\[“

Content Hugging 优先级和Content Compression Resistance 优先级
Content Hugging 优先级:  当内容拉伸时,优先级高的,将不会被拉伸
Content Compression Resistance 优先级: 当内容被压缩时,优先级高的,将不会被压缩
复制代码

看一个 StackOverflow 上的例子:现在有一个这样的按钮 [ Click Me ],已经设置了它与父视图水平方向的约束,并且这些约束的优先级是 500(设置成 500 只是为了方便,默认是 1000 ),然后设置这个按钮水平方向的内容优先级(Content Hugging 和 Content Compression Resistance),根据水平方向的内容优先级就会有不同的效果。
先看 Content Hugging 优先级,如果这个父视图现在要增长,当 Content Hugging 优先级大于 500 时,这个按钮就会这样[Click Me](我不想被拉长),当小于 500 时就会这样[ Click Me ](跟着父视图被拉长了);

再看 Content Compression Resistance 优先级,如果父视图现在要收缩,当 Content Compression Resistance 优先级大于 500 时,这个按钮就会这样[Click Me](我不想被挤压),当小于 500 时就会这样[Cli…](跟着父视图被挤压了)


RACSignal和RACSequence的区别

One is pull driven (RACSequence) and the other is push driven (RACSignal). From here:
Push-driven means that values for the signal are not defined at the moment of signal creation and may become available at a later time (for example, as a result from network request, or any user input). Pull-driven means that values in the sequence are defined at the moment of signal creation and we can query values from the stream one-by-one.
In your case, you make the RACSignal pull-driven, because you are already have its values.


计算文本的行数

Listing 1 Counting hard line breaks

NSString *string;

unsigned numberOfLines, index, stringLength = [string length];

for (index = 0, numberOfLines = 0; index < stringLength; numberOfLines++)
    index = NSMaxRange([string lineRangeForRange:NSMakeRange(index, 0)]);
复制代码

Listing 2 Counting lines of wrapped text

NSLayoutManager *layoutManager = [textView layoutManager];
unsigned numberOfLines, index, numberOfGlyphs =
        [layoutManager numberOfGlyphs];

NSRange lineRange;
for (numberOfLines = 0, index = 0; index < numberOfGlyphs; numberOfLines++){
  (void) [layoutManager lineFragmentRectForGlyphAtIndex:index
           effectiveRange:&lineRange];
   index = NSMaxRange(lineRange);
}
复制代码

ref:developer.apple.com/library/mac…


UICollectionView的不同

//UICollectionView和UITableView的一个不同.

UICollectionViewLayoutAttributes *attributes = [self.collectionView.collectionViewLayout layoutAttributesForItemAtIndexPath:indexPath];


UICollectionView滚动到指定的位置.

UICollectionViewLayout已经提供了两个方法可以实现这个功能:

- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity;
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset NS_AVAILABLE_IOS(7_0);
复制代码

也可以通过代理来完成:

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset 
复制代码

ref:www.cnblogs.com/Phelthas/p/…


关于UICollectionViewFlowLayout

//这里用attributes比用cell要好很多,因为cell可能因为不在屏幕范围内导致cellForItemAtIndexPath返回nil
UICollectionViewLayoutAttributes *attributes = 
[self.collectionView.collectionViewLayout layoutAttributesForItemAtIndexPath:indexPath];
    
复制代码

1.异步加载时,避免因cell的重用而导致的图片对应错误

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
                  cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    BHAlbumPhotoCell *photoCell =
        [collectionView dequeueReusableCellWithReuseIdentifier:PhotoCellIdentifier
                                                  forIndexPath:indexPath];
    BHAlbum *album = self.albums[indexPath.section];
    BHPhoto *photo = album.photos[indexPath.item];
    // load photo images in the background
    __weak BHCollectionViewController *weakSelf = self;
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        UIImage *image = [photo image];
        dispatch_async(dispatch_get_main_queue(), ^{
            // 当cell依然可见的时候,说明异步加载成功时,cell正常对应.UITableView也有同样的方法.
            if ([weakSelf.collectionView.indexPathsForVisibleItems containsObject:indexPath]) {
                BHAlbumPhotoCell *cell =
                    (BHAlbumPhotoCell *)[weakSelf.collectionView cellForItemAtIndexPath:indexPath];
                cell.imageView.image = image;
            }
        });
    }];
    operation.queuePriority = (indexPath.item == 0) ?
        NSOperationQueuePriorityHigh : NSOperationQueuePriorityNormal;
    [self.thumbnailQueue addOperation:operation];
    return photoCell;
}
复制代码

关于UICollectionViewFlowLayout

1.UICollectionView的调用如下方法,会使得,可见的cell重新布局

- (void)performBatchUpdates:(void (^(void))updates completion:(void (^)(BOOL finished))completion;
-(void)reloadData;
复制代码

2.布局的顺序:

A. 如果已经设置的cell的固定大小,则直接进行下一步,否则调用:
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)
来获取cell的大小
B.调用UICollectionViewFlowLayout的prepareLayout,来准备布局,在这里可以进行一些准备工作

C.调用- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect 来设置每一个当前显示cell的大小,层次,transform等,同时,这个方法还可以设置supplementaryView和decorationView的布局属性。合理使用这个方法的前提是不要随便返回所有的属性,除非这个view处在当前collectionView的可视范围内,又或者大量额外的计算造成的用户体验下降
复制代码

3.其它的一些方法

A. 由于collectionView将item的布局任务委托给layout对象,那么滚动区域的大小对于它而言是不可知的。自定义的布局对象必须在这个方法里面计算出显示内容的大小,包括supplementaryView和decorationView在内。
- (CGSize)collectionViewContentSize

B.相当重要的方法。collectionView可能会为了某些特殊的item请求特殊的布局属性,我们可以在这个方法中创建并且返回特别定制的布局属性。根据传入的indexPath调用[UICollectionViewLayoutAttributes layoutAttributesWithIndexPath: ]方法来创建属性对象,然后设置创建好的属性,包括定制形变、位移等动画效果在内
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath

C.当collectionView的bounds改变的时候,我们需要告诉collectionView是否需要重新计算布局属性,通过这个方法返回是否需要重新计算的结果。简单的返回YES会导致我们的布局在每一秒都在进行不断的重绘布局,造成额外的计算任务。
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
复制代码

4.注意的问题:

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath 
上面的indexPath.row并不代表行了,和UITableView中的indexPath.row表示单独的行,是不同的
复制代码

5.decoration view 必须在UICollectionViewFlowLayout中注册的.

Register the emblem view at the end of the setup method on BHPhotoAlbumLayout:

[self registerClass:[BHEmblemView class] forDecorationViewOfKind:BHPhotoEmblemKind];
复制代码

Unlike the other views we’ve added, only the layout class deals with decoration views, thus we perform the registration here rather than in the view controller.

6.在UICollectionViewCell的子类中的prepareForReuse完成重用前的清理工作

- (void)prepareForReuse
{
    [super prepareForReuse];
    self.imageView.image = nil;
}
复制代码

ref:www.cnblogs.com/Phelthas/p/…

7.UICollectionView的一些概念

Supplementary views are usually used to provide extra information about a section within the collection view, effectively replacing the concept of header and footer views in a table view.

Decoration views are used to provide visual ornamentation to the collection view (for example, a background image behind a section, or an ornamental icon at the end of the collection view).

Both supplementary and decoration views must be subclasses of UICollectionReusableView.

8.UICollectionViewUpdateItem当其item属性为NSNotFound的时候,表示这是一个section view

An instance of UICollectionViewUpdateItem can represent either an item or a section update, and the only way to tell which is that update items which represent section updates contain index paths with a valid section index and NSNotFound for the item index, while update items that represent item updates contain index paths with valid item indexes.

  if (updateItem.indexPathAfterUpdate.item == NSNotFound)
   {
    [self.insertedSectionIndices addObject:@(updateItem.indexPathAfterUpdate.section)];
   }
复制代码

ref:Animating Items in a UICollectionView

8.批量更新UICollectionView时的动画.和UITableView差不多吧,把插入,删除,更新的语句放在相应的位置中.

NSMutableArray* indexArray=[[NSMutableArray alloc] init];
    NSInteger count=_imgDataArray.count;
    for(NSInteger i=0;i<photos.count;i++){
        [indexArray addObject:[NSIndexPath indexPathForRow:count+i inSection:0]];
    }
    [self->_imgDataArray addObjectsFromArray:photos];
    dispatch_async(dispatch_get_main_queue(), ^{
        [UIView animateWithDuration:0.5 delay:1 usingSpringWithDamping:0.5 initialSpringVelocity:1 options:UIViewAnimationOptionCurveEaseIn animations:^{
            [self->_imgCollectionView performBatchUpdates:^{
                [self->_imgCollectionView insertItemsAtIndexPaths:indexArray];
            } completion:^(BOOL b){
            }];
        } completion:^(BOOL b){
            [self->_imgCollectionView reloadData];
        }];
});
复制代码

button设置 titleEdgeInsets属性和 imageEdgeInsets属性来调整其image和label相对位置

前提:这是在button的frame足够显示image和label内容的情况下,如果frame不够,图片或者文字会被压缩(demo的button是xib上画的,所以大小刚刚好)

前置知识点:titleEdgeInsets是title相对于其上下左右的inset,跟tableView的contentInset是类似的,
如果只有title,那它上下左右都是相对于button的,image也是一样;
如果同时有image和label,那这时候image的上左下是相对于button,右边是相对于label的;title的上右下是相对于button,左边是相对于image的。如下图

6AA2ED8E-5B00-4CB1-97AE-58BD5D95032A.png

注意这两个属性:

A96EB757-0770-48AE-9931-F4CE61B48845.png
ref: www.cnblogs.com/Phelthas/p/…
stackoverflow.com/questions/4…

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享