2016年5月开发问题记录

这是我最近工作中遇到的问题,做一个简单的记录。

设置导航栏透明

项目中有一个界面的导航栏是透明的,效果如下:

1-1

我首先想到的是:

1
self.navigationController.navigationBar.barTintColor = [UIColor clearColor];

但是设置了没有效果,然后又是一顿Google,找到了如下的解决方案:

1
2
3
4
5
6
// 设置导航栏背景图为一个透明的空图片
[self.lmrNavigationBar setBackgroundImage:[UIImage new] forBarMetrics:UIBarMetricsDefault];
// 设置导航栏的阴影背景图为一个透明的空图片
self.lmrNavigationBar.shadowImage = [UIImage new];
// 设置导航栏为半透明
self.lmrNavigationBar.translucent = YES;

设置UIPageControl左对齐

UIPageControl默认是居中对齐的,要设置它左对齐,需要进行在布局是,根据UIPageControl点的个数,计算UIPageControl的中心点X的坐标。代码如下:

1
2
3
4
5
- (void)layoutSubviews {
CGSize pageSize = [self.pageControl sizeForNumberOfPages:self.pageControl.numberOfPages];
[self.pageControl setCenterX:20 + pageSize.width / 2];
[self.pageControl setOriginY:CGRectGetHeight(self.frame) - 14 - CGRectGetHeight(self.pageControl.frame)];
}

效果如下:

2-1

设置UILabel的内边距

项目中要实现如下的一个标签(不可点击):

3-1

一种方案是直接使用UIButton来实现,因为UIButton有titleEdgeInsets属性,可以设置文字的内边距,我这里尝试使用了UILabel来实现,UILabel默认是不可以设置内边距的,但是可以通过自定义Label来实现,实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#import "LMRPaddingLabel.h"
@implementation LMRPaddingLabel

- (void)drawTextInRect:(CGRect)rect {
UIEdgeInsets insets = {2, 2, 2, 2};
[super drawTextInRect:UIEdgeInsetsInsetRect(rect, insets)];
}

- (CGSize)intrinsicContentSize {
CGSize size = [super intrinsicContentSize];
return CGSizeMake(size.width + 4, size.height + 4);
}

@end

这样,LMRPaddingLabel就会有2个点的内边距,相关问题可参考:ios add left padding to uilabel

长按手势默认会触发两次

给一个View添加了一个UILongPressGestureRecognizer,长按这个View,默认会触发两次,一次是经过了minimumPressDuration长的时间,另一次是手指离开时,可以通过手势的state属性来区分,代码如下:

1
2
3
4
5
6
7
8
9
10
-  (void)handleLongPress:(UILongPressGestureRecognizer*)sender {
if (sender.state == UIGestureRecognizerStateEnded) {
NSLog(@"UIGestureRecognizerStateEnded");
//Do Whatever You want on End of Gesture
}
else if (sender.state == UIGestureRecognizerStateBegan){
NSLog(@"UIGestureRecognizerStateBegan.");
//Do Whatever You want on Began of Gesture
}
}

相关问题可参考:UILongPressGestureRecognizer gets called twice when pressing down

使用RAC绑定可重用的Cell

一般来讲,我们会为每一个复杂View创建一个对应的ViewModel,然后使用RAC(ReactiveCocoa)对View与ViewModel进行绑定(Binding),View与ViewModel之间始终是一一对应的,但是在UITableview中就有问题了,由于UITableviewCell的重用机制,实际创建的Cell个数要少于对应的ViewModel的数量,这样如果还是使用RAC进行简单的数据绑定的话,就会出现重复绑定的问题。

解决方案就是在Cell发生重用后,解除Cell与之前的ViewModel的绑定,然后把Cell与新的ViewModel进行绑定。RAC中有一个方法takeUntil可以实现在指定信号发出前,一直订阅某一信号,一旦指定信号发出,就不再订阅某一信号,就相当于接触绑定。

实例代码如下:

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

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell =
[tableView dequeueReusableCellWithIdentifier:REUSABLE_CELL_ID];

UILabel *label = (UILabel *)[cell viewWithTag:VIEW_TAG];
Model *someModel = [self getModelFromIndexPath:indexPath];

// `takeUntil:` makes the RACObserve() signal complete (and thus breaks the subscription)
// when the cell is recycled.

RAC(label, text) = [RACObserve(someModel, someKey)
takeUntil:cell.rac_prepareForReuseSignal];

return cell;
}

这里信号rac_prepareForReuseSignal即为Cell发生重用时发出的信号。

相关问题可参考:Example of Reactive Cocoa binding for a reusable cell.

使用NSURLSession发送同步请求

最近项目中用到了同步请求,使用NSURLConnection发送同步请求非常简单,直接调用NSURLConnection的类方法+sendSynchronousRequest:就可以了,不过由于苹果官方不建议使用NSURLConnection,而NSURLSession又没有直接的同步请求的方法,所以就查了一下如何使用NSURLSession发送同步请求,结果如下,可以使用下面的方法发送同步请求:

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
#import "NSURLSession+SynchronousTask.h"

@implementation NSURLSession (SynchronousTask)

#pragma mark - NSURLSessionDataTask

- (nullable NSData *)sendSynchronousDataTaskWithURL:(nonnull NSURL *)url returningResponse:(NSURLResponse *_Nullable*_Nullable)response error:(NSError *_Nullable*_Nullable)error {
return [self sendSynchronousDataTaskWithRequest:[NSURLRequest requestWithURL:url] returningResponse:response error:error];
}

- (nullable NSData *)sendSynchronousDataTaskWithRequest:(nonnull NSURLRequest *)request returningResponse:(NSURLResponse *_Nullable*_Nullable)response error:(NSError *_Nullable*_Nullable)error {
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block NSData *data = nil;
[[self dataTaskWithRequest:request completionHandler:^(NSData *taskData, NSURLResponse *taskResponse, NSError *taskError) {
data = taskData;
if (response) {
*response = taskResponse;
}
if (error) {
*error = taskError;
}
dispatch_semaphore_signal(semaphore);
}] resume];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

return data;
}

@end

这里使用的GCD的信号量做同步,使用时直接调用NSURLSession的这个分类的方法即可实现同步请求。

iOS7上自动布局的坑:Assertion failure in -[UITableView layoutSublayersOfLayer:]的错误

最近在开发中遇到了“Assertion failure in -[UITableView layoutSublayersOfLayer:]”错误,而且只在iOS7上出现,iOS8及以上都是正常了,于是一阵Google,发现原来这个iOS7上自动布局的一个坑,造成这个错误主要有两个原因:

  1. 在添加UITableviewCell的子视图时,加在了Cell上而不是加在Cell的ContentView上,并且使用自动布局来布局
  2. 给UITableview添加了子视图,并且使用自动布局来布局,即[tableView addSubview:someView]

对于第一个问题比较好解决,一般Cell的子视图都是加在Cell的ContentView上的,如果有那个视图没有加在ContentView上,改过来就可以了,只不过找起来可能比较麻烦。

第二个问题就比较麻烦了,有时候我们确实需要给UITableview上加一个子视图,例如,加一个ErrorView或LoadingView等,这是如果使用自动布局设置了这个子视图与Tableview之间的约束,就会出现这个错误。解决方法就是不要使用自动布局,改用Frame的方法去设置子视图的位置,这样就不会报错了。针对iSO7专门做一下适配,如果大于iOS7可以继续使用自动布局。

因为自动布局是在iOS6上才出现的,所以iOS6,包括iOS7上的自动布局都不是很成熟,有很多坑,这就需要我们不多的总结,才能避免再次掉进坑里。

Masonry不能用来更新XIB上设置的约束

使用XIB加载的视图,如果在XIB上设置了自动布局的约束,在代码中想更新约束,是不能使用Masonry的mas_updateConstraints方法来更新约束的。解决方法就是把要更新的约束通过IBOutlet连接到代码中,然后在代码里更新这个约束,不过这个方法比较麻烦,暂时还没有找到其他更好的方法。