123. UITableView 中如何禁止某个 cell 可被点击?
124. 为什么有的 UIView 的 drawRect 方法不自动触发?
128. JSONModel 中访问某个属性时老是出现 doeseNotRecognizeSelector 错误
130. 如何让 UIButton 的 image 占满整个控件?
131. 如何删除 Xcode 中的 Provisioning file?
133. Failed to set (ovalLineWidth) user defined inspected property on (UIView)
135. scheduledTimerWithTimeInterval unrecognized selector
137. 上传商店时,环信框架出错unsupported architectures ‘[x86_64, i386]’
140. Unbalanced calls to begin/end appearance transitions transitionFromViewController
123. UITableView 中如何禁止某个 cell 可被点击?
// 禁止重复点击 cell
- (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath{
// 如果不想让某行可点,返回 nil,否则返回 indexPath
return indexPath.row == currentProgram ? nil : indexPath;
}
124. 为什么有的 UIView 的 drawRect 方法不自动触发?
UIView 的 drawRect 方法会按照每帧刷新的频率由 UIKit 进行调用。但是对于没有 frame 的 View,这个机制无法生效。因此,如果没有用 initWithFrame 方法初始化的 view,drawRect 方法不会随帧率调用(但在初始化成功后会有 2 次调用)。
对于这种 view,你可以在 CADisplayLink 的基础上,联合 setNeedDisplay 方法来模拟 drawRect 的自动刷新。
返回目录
125. 如何对自定义对象进行排序?
首先为自定义对象定义一个 compare: 方法:
@implementation AudioRecordInfo
- (NSComparisonResult)compare:(AudioRecordInfo *)otherInfo {
return [self.createTime compare:otherInfo.createTime];
}
...
这个方法用于通过 createTime 属性来决定两个自定义对象的大小。
然后定义一个自定义对象数组:
NSArray<AudioRecordInfo*>* models;
加载数组元素,比如从一个 plist 文件中加载自定义对象数组:
models = readAudioPlist().allValues ;
对数组进行排序:
// 1. 正序
models = [models sortedArrayUsingSelector:@selector(compare:)];
// 2. 倒序
models = [[models reverseObjectEnumerator] allObjects];
126. 如何重复一个字符串 n 次以构建另一个字符串?
NSString oldStr = @"ABC";
NSString newStr = [@"" stringByPaddingToLength:oldStr.length*3 withString:oldStr startingAtIndex:0];
这会在 “ABC” 的基础上重复 3 次,结果是 “ABCABCABC”。
返回目录
127. 调用一个块的时候程序崩溃
在 O-C 中向一个 nil 对象发送消息是安全的,但块不同,块实际上是 c 语言的特性,因此你不能向一个 nil 块进行调用。因此,在调用块之前,请检查块是否为 nil:
if(block!=nil) block();
128. JSONModel 中访问某个属性时老是出现 doeseNotRecognizeSelector 错误。
当模型嵌套尤其是一个模型拥有另一个模型的结合属性时,容易出现此错误。
比如:
@interface TimeSpanModel:JKModel
@property(strong,nonatomic)NSString* date;
@end
@interface AttendanceListResult : JKModel
@property(assign,nonatomic)BOOL succeed;
@property(assign,nonatomic)int code;
@property(strong,nonatomic)NSString* msg;
@property(strong,nonatomic)NSArray<TimeSpanModel*>* data;
@end
这里 AttendanceListResult 的 data 属性是一个 TimeSpanModel 数组(通过泛型类型指定)。这时访问 AttendanceListResult 的 data 属性会导致 doeseNotRecognizeSelector 错误。
因为这里的 NSArray
@property(strong,nonatomic)NSArray<TimeSpanModel>* data;
同时定义一个 TimeSpanModel 协议:
@protocol TimeSpanModel
@end
协议不需要实现,声明就可。这样,上述问题可以解决。
返回目录
129. 1 像素问题
有时候需要在 View 上显示 1 像素高度的细线(分割线),通常我们会在 IB 中用一个 UIView 或者 UILabel 来代替,并将它的 高度设置为 1。
但在 UIKit 中的 1 个单位的高度在不同的设备上会有不同的表达,比如在 retina 屏上 1 个单位等于 2 个像素,在 retina HD (iphone6/6p) 上为 3 个像素,因此高度为 1 的细线在不同设备上并不是真正的 1 个像素粗细。
对于这个问题,我们需要定义一个 UIView 或 UILabel 子类,并覆盖它的 awakeFromNib 方法:
-(void)awakeFromNib {
[super awakeFromNib];
self.layer.borderColor = [self.backgroundColor CGColor];
self.layer.borderWidth = (1.0 / [UIScreen mainScreen].scale) / 2;
self.backgroundColor = [UIColor clearColor];
}
然后选中我们刚刚拖到 IB 中的那个 UIView 或 UILabel,将它 的 class 指定为这个 UIView 或 UILabel 子类。
回到目录
130. 如何让 UIButton 的 image 占满整个控件?
self.btnAvatar.contentHorizontalAlignment = UIControlContentHorizontalAlignmentFill;
self.btnAvatar.contentVerticalAlignment = UIControlContentVerticalAlignmentFill;
这样,用 setImage:forState 设置 UIButton 的 image 后,图片会占满整个控件,同时保持比例不失真。回到目录
131. 如何删除 Xcode 中的 Provisioning file?
Xcode 的 provisioning 文件存放地址:
~/Library/MobileDevice/Provisioning Profiles
132. 为什么 MJRefresh 下拉时会闪退?
请保证 endRefreshing 一句调用时,数据源的数目没有被意外更改。比如我们一般会在下拉时重新加载数据源:
_tableView.mj_header=[MJRefreshNormalHeader headerWithRefreshingBlock:^{
[_models removeAllObjects];
[self loadNoticeList:1 success:^(NSArray<CampusNoticeModel *> *data) {
[_tableView.mj_header endRefreshing];
[_models addObjectsFromArray:data];
...... // 其它代码
} failure:^(NSString *msg) {
[_tableView.mj_header endRefreshing];
}];
}];
这会导致一个崩溃。因为 endRefreshing 会导致 [UIScrollView setContentOffset:] 调用,进而调用 tableView 的 cellForIndexPath:] 数据源方法,但是却不会调用另外两个数据源方法 numberOfRowsInSection 和 numberOfSectionsInTableView。
因为调用 endRefreshing 之时,我们已经修改了数据源的数目(即removeAllObjects一句),这会导致 cellForIndexPath: 方法会根据错误的行数和节数(很大可能是上一次调用 numberOfRowsInSection 和 numberOfSectionsInTableView 的数目)去访问 _models 数组,从而导致数组下标越界。如果查看控制台输出,你会看到:
*** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayM objectAtIndex:]: index 3 beyond bounds for empty array'
因此上述代码应当改为:
_tableView.mj_header=[MJRefreshNormalHeader headerWithRefreshingBlock:^{
[self loadNoticeList:1 success:^(NSArray<CampusNoticeModel *> *data) {
[_tableView.mj_header endRefreshing];
[_models removeAllObjects];
[_models addObjectsFromArray:data];
...... // 其它代码
} failure:^(NSString *msg) {
[_tableView.mj_header endRefreshing];
}];
}];
唯一的修改是将对数据源数目的修改(removeAllObjects)放在了 endRefreshing 之后。但是这样仍然存在一个问题,即先上拉,再下拉,仍然会导致闪退。这仍然是因为数据源数目发生改变(上拉加载导致)导致行数/节数计算不正确的原因。所以应当把上述代码修改为:
_tableView.mj_header=[MJRefreshNormalHeader headerWithRefreshingBlock:^{
[_tableView.mj_header endRefreshing];
[self loadNoticeList:1 success:^(NSArray<CampusNoticeModel *> *data) {
[_models removeAllObjects];
[_models addObjectsFromArray:data];
...... // 其它代码
} failure:^(NSString *msg) {
}];
}];
也就是 endRefreshing 放到第一句执行。
这个问题的根源是由于 UITableView 在发现 contentInsets 被改变之后,会自动调用 cellForIndexPath 刷新表格,但同时却不会调用 numberOfRowsInSection 和 numberOfSectionsInTableView。这也是可以理解的,毕竟正常情况下 contentInsets 的改变会导致部分单元格显示/隐藏,但数据源数目是不会变的,因此刷新表格时重新计算行数和节数是没有必要的。但是却没有考虑到 MJRefresh 等下拉刷新框架正是通过对 contentInsets 的改变来触发数据源的拉取。
因此在修改表格的 contentInsets 之前请保证表格的行数/节数没有发生改变。
133. Failed to set (ovalLineWidth) user defined inspected property on (UIView)
这不会导致崩溃,只是会在控制台中输出上述提示而已。打开报错的 xib/storyboard,如果找不到可以搜索上述提示中属性名称(比如 ovalLineWidth),在 Identity 面板的 UserDefinedRuntime Attributes 一栏,将报错的属性从列表中删除。
~/Library/MobileDevice/Provisioning Profiles
134. 如何在播放音视频时,拖动播放进度时让滑块不倒退
使用 AVPlayer 时,如果用户拖动进度,滑块会出现短暂的后退,然后才会从正确的位置开始播放。要解决这个问题,需要:
- 现将 UISlider 的 continuous 设置为 NO,这样不会在用户拖动的过程中不停的发送 valueUpdate 事件,而是只在用户手指离开时发送一次 valueUpdate 事件,从而避免不必要的 seekToTime 动作。
- 声明一个 BOOL 类型的实例变量 seeking,用于标识是否正在 seekToTime。
在 UISlider 的 valueUpdate 事件方法中,在 seekToTime 之前,将 seeking 置为 YES,在 seekToTime 结束时置为 NO:
seeking = YES; [player seekToTime:time toleranceBefore:kCMTimeIndefinite toleranceAfter:kCMTimeIndefinite completionHandler:^(BOOL finished){ seeking = NO; }];
在 AVPlayer 的 timeObserver 中,对 seeking 进行判断。只有 seeking == NO 时才更新 UISlider 的值:
void (^timeBlock)(CMTime)=^(CMTime time) { float current = CMTimeGetSeconds(item.currentTime); float total = CMTimeGetSeconds(item.duration); if (current && !seeking) { weakSelf.slProgress.value = current / total; weakSelf.lbCurrentDuration.text = [NSString stringWithFormat:@"%02d:%02d",(int)current/60,(int)current%60]; } }; if(timeObserver == nil ){ timeObserver = [player addPeriodicTimeObserverForInterval:tm queue:dispatch_get_main_queue() usingBlock:timeBlock]; }
135. scheduledTimerWithTimeInterval unrecognized selector
scheduledTimerWithTimerInterval 是 iOS 10 以后新增的 API,在 iOS 9 以前会报这个错误。为兼容 iOS 9 以前,你需要调用 timerWithTimeInterval 方法替代:
NSTimer *newGraceTimer = [NSTimer timerWithTimeInterval:self.graceTime target:self selector:@selector(handleGraceTimer:) userInfo:nil repeats:NO];
[[NSRunLoop currentRunLoop] addTimer:newGraceTimer forMode:NSRunLoopCommonModes];
注意,别忘了将 NSTimer 添加到 runloop 中,否则不会生效。
136.修改 Tabbar 高度和位置
// 1. 修改 tabBar 的高度
tabBarController.tabBar.frame = CGRectMake(0,HEIGHT-70, WIDTH, 70);
NSArray*array = [tabBarController.view subviews];
for (int i =0; i<5; i++) {
UIView *transtionView =array[0];
transtionView.height = HEIGHT -70;
}
// 2. 修改 tabBarItem 图标的位置
for (UITabBarItem *item in tabBarCtl.tabBar.items) {
item.imageInsets = UIEdgeInsetsMake(8,0, -8, 0);
}
// 3. 修改 tabBarItem 文字标签位置
[[UITabBarItem appearance] setTitlePositionAdjustment:UIOffsetMake(0, 12)];
137. 上传商店时,环信框架出错unsupported architectures ‘[x86_64, i386]’
App 中集成了环信框架 HyphenateLite.framework,在开发环境中没有任何问题,但在提交商店时出现错误:
ERROR ITMS-90087: "Unsupported Architectures. The executable for Client.app/Frameworks/HyphenateLite.framework contains unsupported architectures '[x86_64, i386]'."
ERROR ITMS-90087: "Unsupported Architectures. The executable for Client.app/Frameworks/HyphenateLite.framework contains unsupported architectures '[x86_64, i386]'."
ERROR ITMS-90209: "Invalid Segment Alignment. The app binary at 'Client.app/Frameworks/HyphenateLite.framework/HyphenateLite' does not have proper segment alignment. Try rebuilding the app with the latest Xcode version."
ERROR ITMS-90209: "Invalid Segment Alignment. The app binary at 'Client.app/Frameworks/HyphenateLite.framework/HyphenateLite' does not have proper segment alignment. Try rebuilding the app with the latest Xcode version."
ERROR ITMS-90125: "The binary is invalid. The encryption info in the LC_ENCRYPTION_INFO load command is either missing or invalid, or the binary is already encrypted. This binary does not seem to have been built with Apple's linker."
这是因为环信框架为了支持在虚拟机上进行编译,增加了 x86_64 和 i386 架构,需要在提交商店时去掉。打开 Build Phases,点击左上角 + 号,添加一个 Run Script,在 Shell 栏输入 /bin/sh,脚本内容输入:
APP_PATH="${TARGET_BUILD_DIR}/${WRAPPER_NAME}"
# This script loops through the frameworks embedded in the application and
# removes unused architectures.
find "$APP_PATH" -name '*.framework' -type d | while read -r FRAMEWORK
do
FRAMEWORK_EXECUTABLE_NAME=$(defaults read "$FRAMEWORK/Info.plist" CFBundleExecutable)
FRAMEWORK_EXECUTABLE_PATH="$FRAMEWORK/$FRAMEWORK_EXECUTABLE_NAME"
echo "Executable is $FRAMEWORK_EXECUTABLE_PATH"
EXTRACTED_ARCHS=()
for ARCH in $ARCHS
do
echo "Extracting $ARCH from $FRAMEWORK_EXECUTABLE_NAME"
lipo -extract "$ARCH" "$FRAMEWORK_EXECUTABLE_PATH" -o "$FRAMEWORK_EXECUTABLE_PATH-$ARCH"
EXTRACTED_ARCHS+=("$FRAMEWORK_EXECUTABLE_PATH-$ARCH")
done
echo "Merging extracted architectures: ${ARCHS}"
lipo -o "$FRAMEWORK_EXECUTABLE_PATH-merged" -create "${EXTRACTED_ARCHS[@]}"
rm "${EXTRACTED_ARCHS[@]}"
echo "Replacing original executable with thinned version"
rm "$FRAMEWORK_EXECUTABLE_PATH"
mv "$FRAMEWORK_EXECUTABLE_PATH-merged" "$FRAMEWORK_EXECUTABLE_PATH"
done
138. ipa 上传成功,但是在构建版本中看不到
请检查 info.plist 中是否声明了App 所需要的权限,以及权限描述字串是否不为空。
回到目录
139. 为什么 debug 导航器不显示内存、cpu?
打开 Product > Scheme > Edit Scheme,在 Run/Diagnostics/Memory Management 中,将 Zombie Objects 选项反选。
140. Unbalanced calls to begin/end appearance transitions transitionFromViewController
通常会在 transitionFromViewController 方法时收到这个警告。在 transitionFromViewController 调用之前,不要将子控制器的 view 添加到 subviews 中去。将 addSubview 自控制器的 view 移到 transitionFromViewController 方法块中,这个警告消失。
141. 当cell的数量占不满一屏时,如何去掉无用的cell分割线?
如果表格是分组的,请将 table view 的 style 设置为 Grouped,否则为表格添加一个 0.01 高度的 footer:
-(CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section
{
return 0.01f;
}
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section
{
return [UIView new];
}