2016年6月开发问题记录

继续记录6月份开发中遇到的问题。

UIBarButtonItem点击区域的问题

今天在定制导航上的返回按钮遇到了一个问题,顺便记录一下。

一般常规的定制导航栏返回按钮的方法如下:

1
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"back"] style:UIBarButtonItemStyleDone target:self action:@selector(clickBackBtn:)];

这样写有一个问题,运行代码后,返回按钮的可点击区域如下图所示:

1

这并不是我想要的,点击区域太大,完全没有点到返回按钮上都可以触发返回操作。于是换一种思路,用自定义View实现,代码如下:

1
2
3
4
UIButton *backBtn = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 30, 44)];
[backBtn addTarget:self action:@selector(clickBackBtn:) forControlEvents:UIControlEventTouchUpInside];
[backBtn setImage:[UIImage imageNamed:@"back"] forState:UIControlStateNormal];
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:backBtn];

编译运行,效果依然一样,点击区域还是太大,如下图:

2

这我就不明白了,于是一阵Google,找到了一个解决方案,代码如下:

1
2
3
4
5
6
UIButton *backBtn = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 30, 44)];
[backBtn addTarget:self action:@selector(clickBackBtn:) forControlEvents:UIControlEventTouchUpInside];
[backBtn setImage:[UIImage imageNamed:@"back"] forState:UIControlStateNormal];
UIView *containerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 30, 44)];
[containerView addSubview:backBtn];
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:containerView];

这样点击区域就正常了,如下图:

3

其实就是在Button外面又包了一层View,这样点击区域就只有Button那么大,我怀疑,可能是加载BarButtonItem上的自定义View都会被自动的拉长,暂时还没找到其他解决方案。

Reference:UIBarButtonItem tap area

将自定义对象作为NSDictionary的Key

将自定义对象作为NSDictionary的Key,需要重写自定义isEqual方法和hash方法。isEqual方法比较简单,如下代码:

1
2
3
4
5
6
7
8
9
- (BOOL)isEqual:(id)object {
Student *st = (Student *)object;
if ([st.name isEqualToString:self.name] && st.age == self.age) {
return YES;
}
else {
return NO;
}
}

但是如果要把自定义的对象作为NSDictionary的Key,光重写isEqual方法是不够的,还需要重写hash方法,如果不重写hash方法就会导致字典不能正确的添加和删除。重写hash函数的方法很多,可以通过构建不同散列函数来实现,但是无论哪种方法,目的都是尽可能的确保相同的对象返回的hash值相同,一个简单的方法就是使用每一个属性的hash的异或的结果来作为这个对象的hash值。代码如下:

1
2
3
- (NSUInteger)hash {
return [self.name hash] ^ self.age;
}

这样构建的hash值冲突的概率很小。另外,要作为字典的Key,还必须实现NSCopying协议。因为不需要做深拷贝,所以只需要简单的返回self即可,代码如下:

1
2
3
- (id)copyWithZone:(NSZone *)zone {
return self;
}

这个构建的一个对象就可以作为NSDictionary的Key了。

Reference:Equality

UITableViewCell的“no index path for table cell being reused”问题

项目中有一个自定义的UITableViewCell,由于版本更新,要把这个Cell改为可以悬停的SectionHeader,为了图方便,我没有重新自定义一个View,而是直接在viewForHeaderInSection:方法里返回了cell,代码如下:

1
2
LMBookWriteCommentCell *cell = (LMBookWriteCommentCell *)[self tableView:self.tableView writeCommentCellWithIndexPath:nil];
return cell;

这样写有个问题,就是在reloadData时,这SectionHeader就消失了,变成了空白的一片,并且出现了“no index path for table cell being reused”错误,程序没有崩溃,但是就是看不见Cell上的内容。于是一阵Google,终于找到了答案,只要把上面的代码的return cell;改为return cell.contentView;就可以了。

Reference:What is the meaning of the “no index path for table cell being reused” message in iOS 6/7?

Autolayout动画的正确姿势

做Autolayout动画的一般步骤是:

  1. 修改约束,如果使用Masonry,使用mas_updateConstraints方法修改约束,如果使用Xib,则直接把要修改的约束通过拉线链接到代码里进行修改即可。
  2. 在动画的block中,调用View的layoutIfNeed()方法。

这里要注意的一点是第二步,如果直接调用view.layoutIfNeed(),可能会导致动画不能进行,正确的方法应该是调用view.superview.layoutIfNeed(),这样才能正确的进行动画。完整代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class ViewController: UIViewController {

@IBOutlet weak var myView: UIView!
@IBOutlet weak var topConstraint: NSLayoutConstraint!

override func viewDidLoad() {
super.viewDidLoad()
}

override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
topConstraint.constant += 100
UIView.animateWithDuration(2.0) {
self.myView.superview?.layoutIfNeeded()
};
}
}

在iOS7上UILabel的省略点颜色不正确

在iOS7上,有时UILabel的省略点的颜色与文字的颜色不同,如下图:

uilable color error

网上查了一下,这应该是iOS7的一个Bug,解决方法就是不要使用textColor设置文字颜色,而使用attributeText,代码如下:

1
2
NSAttributedString *attStr = [[NSAttributedString alloc] initWithString:self.titleLabel.text attributes:@{NSForegroundColorAttributeName:style.fColor, NSFontAttributeName:style.font}];
self.titleLabel.attributedText = attStr;

参考:UILabel dotted line color bug in iOS 7.1

iOS7上,给UICollectionViewCell添加长按手势的坑

项目中有一个需求,当长按任何一个UICollectionViewCell时,刷新UICollectionView,每一个Cell的右上角都出现一个选择按钮。效果如下:

iOS7 UICollectionView longPress

这是一个比较常见的需求,我的做法是在每一个Cell上添加一个长按手势,然后,在手势触发时,刷新这个UICollectionView,然每一个Cell都显示选择按钮。这样做在iOS8,iOS9上都没有问题,当时在iOS7上却出现了问题。在iOS7上,当触发某一个Cell的长按手势之后,这个Cell就不再响应UICollectionView的collectionView:didSelectItemAtIndexPath:方法了。

网上没有找到类似的情况,只能自己解决。最终找到了一种解决方案,代码如下:

1
2
3
4
// 必须重新放在主线程队列中,否则在iOS7上回出现Cell不触发didSelect的情况
dispatch_async(dispatch_get_main_queue(), ^{
[self.collectionView reloadData];
});

要将reloadData重新放在主线程队列中,这样就不会出现这种情况。具体原因不是非常明白,但是,这样确实可以完美的解决这个问题。