iOS 问与答(123-141)

123. UITableView 中如何禁止某个 cell 可被点击?

124. 为什么有的 UIView 的 drawRect 方法不自动触发?

125. 如何对自定义对象进行排序

126. 如何重复一个字符串 n 次以构建另一个字符串

127. 调用一个块的时候程序崩溃

128. JSONModel 中访问某个属性时老是出现 doeseNotRecognizeSelector 错误

129. 1 像素问题

130. 如何让 UIButton 的 image 占满整个控件?

131. 如何删除 Xcode 中的 Provisioning file?

132.为什么 MJRefresh 下拉时容易闪退?

133. Failed to set (ovalLineWidth) user defined inspected property on (UIView)

134. 如何在播放音视频时,拖动播放进度时让滑块不倒退

135. scheduledTimerWithTimeInterval unrecognized selector

136. 修改 Tabbar 高度和位置

137. 上传商店时,环信框架出错unsupported architectures ‘[x86_64, i386]’

138. ipa 上传成功,但是在构建版本中看不到

139. 为什么 debug 导航器不显示内存、cpu?

140. Unbalanced calls to begin/end appearance transitions transitionFromViewController

141. 当cell的数量占不满一屏时,如何去掉无用的cell分割线?

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 时,如果用户拖动进度,滑块会出现短暂的后退,然后才会从正确的位置开始播放。要解决这个问题,需要:

  1. 现将 UISlider 的 continuous 设置为 NO,这样不会在用户拖动的过程中不停的发送 valueUpdate 事件,而是只在用户手指离开时发送一次 valueUpdate 事件,从而避免不必要的 seekToTime 动作。
  2. 声明一个 BOOL 类型的实例变量 seeking,用于标识是否正在 seekToTime。
  3. 在 UISlider 的 valueUpdate 事件方法中,在 seekToTime 之前,将 seeking 置为 YES,在 seekToTime 结束时置为 NO:

    seeking = YES;
    [player seekToTime:time  toleranceBefore:kCMTimeIndefinite toleranceAfter:kCMTimeIndefinite completionHandler:^(BOOL finished){
        seeking = NO;
    }];
  4. 在 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];   
} 

回到目录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值