目录
41.当收到推送通知时, didReceiveRemoteNotification 方法不调用
47. UITextView 在 iOS 7 上文字不能顶部对齐。
48.在数组中删除对象时出错:“Collection was mutated while being enumerated”
49. 当我修改 cell 背景色时,AccessoryView 的背景色仍然是原来的。如何让 AccessoryView 的背景色也做同样改变?
52.当TableView 滚动后,直到 searBar 不可见,如何再次让 searchBar 可见?
39. 定制 cell 的 Edit View
- 实现数据源方法 canEditRowAtIndexPath ,并根据 IndexPath 返回一个 Bool 值——YES 表示支持滑动操作,NO 表示不支持。
- 实现数据源方法 commitEditingStyle 。空实现,不需要编写任何代码。
- 实现委托方法 editActionsForRowAtIndexPath:
-(NSArray *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewRowAction *editAction = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleNormal title:@"Clona" handler:^(UITableViewRowAction *action, NSIndexPath *indexPath){
//insert your editAction here
}];
editAction.backgroundColor = [UIColor blueColor];
UITableViewRowAction *deleteAction = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleNormal title:@"Delete" handler:^(UITableViewRowAction *action, NSIndexPath *indexPath){
//insert your deleteAction here
}];
deleteAction.backgroundColor = [UIColor redColor];
return @[deleteAction,editAction];
}
40. iOS 9 中添加 http 请求白名单
iOS 9 下,默认不再支持 http 类型的网络请求(强制要求 https),如果在代码中发出 http 请求,iOS 会报如下错误:
App Transport Security has blocked a cleartext HTTP (http://) resource load since it is insecure. Temporary exceptions can be configured via your app’s Info.plist file.
我们需要让 iOS 9 支持 http 协议请求,或者将要访问的 http 网址添加到 “白名单” 中。
方法一. 在项目的info.plist中添加一个Key:NSAppTransportSecurity,类型为字典类型。
然后给它添加一个Key:NSAllowsArbitraryLoads,类型为Boolean类型,值为YES;
方法二.
1)、在项目的info.plist中添加一个Key:NSAppTransportSecurity,类型为字典类型。
2)、然后给它添加一个NSExceptionDomains,类型为字典类型;
3)、把需要的支持的域添加給NSExceptionDomains。其中域作为Key,类型为字典类型。
4)、每个域下面需要设置3个属性:NSIncludesSubdomains、NSExceptionRequiresForwardSecrecy、NSExceptionAllowsInsecureHTTPLoads。
均为Boolean类型,值分别为YES、NO、YES。
41.当收到推送通知时, didReceiveRemoteNotification 方法不调用
在实现推送通知时,实现了 didReceiveRemoteNotification 和 didReceiveRemoteNotification:fetchCompletionHandler 方法,但这些方法只会在 App 从后台被唤醒时触发。也就说当 Push 通知到达,用户从通知中心点击消息,系统唤醒 App 后才会触发这些方法。
如果想在 App 处于后台时就调用这些方法,需要使用“静默的推送通知”:
打开后台运行选项中的 Remote notifications:
在通知载体中添加 content-available 键:
{ aps = { "content-available" : 1, sound : "" }; }
42.接收到推送通知时,如何更新 Badge 数?
一般情况下,通知未读数由服务端维护。当服务器发送一个远程通知到某台设备时,同时会在载体中附加 badge 数。当设备收到通知后,App 如果处于后台或退出状态,OS 会自动更新 App 图标上的 badge 数。一旦 App 再次处于运行状态,就可以通过 application:didReceiveRemoteNotification: 方法获取读取远程通知,并从 userInfo 参数中获得 bdge 数。这时你可以更新 badge 数。
if (userInfo) {
NSLog(@"%@",userInfo);
if ([userInfo objectForKey:@"aps"]) {
if([[userInfo objectForKey:@"aps"] objectForKey:@"badgecount"]) {
[UIApplication sharedApplication].applicationIconBadgeNumber = [[[userInfo objectForKey:@"aps"] objectForKey: @"badgecount"] intValue];
}
}
}
注意,在消息载体中 badge 字段要用数字而不是字符串。例如:
{“aps”:{“alert”:”Hello from APNs Tester.”,”badge”:1}}
43.为什么收到推送通知时,不会播放声音?
要在消息载体中指定 sound 字段。例如:
{"aps":{"alert":"Hello from APNs Tester.","badge":1,sound:"default"}}
“sound”:”default”,播放系统默认的声音。
返回目录
44.如何在消息载体中指定自定义的声音?
{"aps":{"alert":"Hello from APNs Tester.","badge":1,"sound":"alarm"}}
其中 alarm 是声音文件的文件名(不需要扩展名)。声音文件必须位于 bundle 或者 Library/Sounds 目录,文件类型必须是 aiff, wav, 或 caf。如果指定的文件不存在,系统用默认声音(default)替代。
返回目录
45.如何读取系统声音?
系统声音放在了 /System/Library/Audio/UISounds 目录,你可以将系统声音拷贝到 App 的 Library/Sounds 目录:
NSArray* loadAudioList(){
NSMutableArray *audioFileList = [[NSMutableArray alloc] init];
NSFileManager *fileManager = [[NSFileManager alloc] init];
NSURL *directoryURL = [NSURL URLWithString:@"/System/Library/Audio/UISounds"];
NSArray *keys = [NSArray arrayWithObject:NSURLIsDirectoryKey];
NSDirectoryEnumerator *enumerator = [fileManager
enumeratorAtURL:directoryURL
includingPropertiesForKeys:keys
options:0
errorHandler:^(NSURL *url, NSError *error) {
// Handle the error.
// Return YES if the enumeration should continue after the error.
return YES;
}];
for (NSURL *url in enumerator) {
NSError *error;
NSNumber *isDirectory = nil;
if (! [url getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:&error]) {
// handle error
}
else if (! [isDirectory boolValue]) {
[audioFileList addObject:url];
if (![fileManager fileExistsAtPath:LIBRARY_SOUNDS_DIR]) {
[fileManager createDirectoryAtPath:LIBRARY_SOUNDS_DIR
withIntermediateDirectories:NO
attributes:nil error:nil];
}
NSString* toDir = [LIBRARY_SOUNDS_DIR stringByAppendingPathComponent:url.lastPathComponent];
NSURL* toUrl=[NSURL fileURLWithPath:toDir];
if (![fileManager fileExistsAtPath:toDir]) {
[fileManager copyItemAtURL:url toURL:toUrl error:&error];
if (error!=nil) {
NSLog(@"复制文件错误:%@",error.localizedDescription);
}
}
}
}
return audioFileList;
}
这样你就可以在推送通知载体中使用系统声音了:
{"aps":{"alert":"Hello from APNs Tester.","badge":"1","sound":"sms-received3.caf"}}
注意 :实测中发现(版本 iOS 8.1.3),把声音文件放在 Library/Sounds 目录是无效的(完整路径是:/var/mobile/Containers/Data/Application/65B983BC-2400-4759-9EE2-247B234597F0/Library/Sounds),iOS 在收到通知后完全无法找到,它仍然播放系统默认的 default 声音。但是将声音文件拷贝到 App Bundle 中是可行的。因此,我们可以从 Library/Sounds 中将声音文件再次拷贝到项目目录中(使用 iExplorer 工具),并确保 Copy Bundle Resouces 中一定要包含这些文件。
46. 如何将中文转换为拼音?
以下代码可获取每个汉字拼音的首字母:
+(NSString *) getFirstLetter:(NSString *) strInput{
if ([strInput length]) {
NSMutableString *ms = [[NSMutableString alloc] initWithString:strInput];
// 1. kCFStringTransformMandarinLatin 表示中文转拉丁字母,NULL 表示转换范围为整个字符串
CFStringTransform((__bridge CFMutableStringRef)ms, NULL, kCFStringTransformMandarinLatin, NO);
// 2. kCFStringTransformStripDiacritics,去掉音调
CFStringTransform((__bridge CFMutableStringRef)ms, 0, kCFStringTransformStripDiacritics, NO);
// 3. 转换结果是按将个汉字的拼音以空格分隔的,我们将每个汉字的拼音按空格切开放到数组中
NSArray *pyArray = [ms componentsSeparatedByString:@" "];
if(pyArray && pyArray.count > 0){
ms = [[NSMutableString alloc] init];
// 4. 只取每个汉字的首字母
for (NSString *strTemp in pyArray) {
[ms appendString:[strTemp substringToIndex:1]];
}
return [ms uppercaseString];
}
ms = nil;
}
return nil;
}
47. UITextView 在 iOS 7 上文字不能顶部对齐。
iOS7上UITextView在UINavigationController中垂直显示存在问题,本来文字在textview中应该垂直顶端对齐的确好象变成底端对齐了,顶端会空出一块。这个问题从ios7开始出现。
解决这个问题,需要在 ViewDidLoad 方法加上了如下代码:
if([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0){
self.automaticallyAdjustsScrollViewInsets = NO; // Avoid the top UITextView space, iOS7 (~bug?)
}
48.在数组中删除对象时出错:“Collection was mutated while being enumerated”
不能在遍历一个 MutableArray 的过程中修改数组(包括添加、删除、替换),以删除为例:
for (APContact* contact in _selectedPeople) {
if (contact.recordID == person.recordID) {
[_selectedPeople removeObject:contact];
}
}
而应当修改成:
id foundObj = nil;
for (APContact* contact in _selectedPeople) {
if (contact.recordID == person.recordID) {
foundObj = contact;
}
}
if (foundObj != nil){
[_selectedPeople removeObject:foundObj];
}
或者在删除之后立即 break(如果一次只删除一个对象的话):
for (APContact* contact in _selectedPeople) {
if (contact.recordID == person.recordID) {
[_selectedPeople removeObject:contact];
break;
}
}
49. 当我修改 cell 背景色时,AccessoryView 的背景色仍然是原来的。如何让 AccessoryView 的背景色也做同样改变?
用 cell.backgroundColor 而不是 cell.contentView.backgroundColor:
cell.backgroundColor = [UIColor colorWithWhite:0.75 alpha:0.5];
50. 如何使用 UISearchController
iOS 8 中新增了 UISearchController,比 UISearchBar 的使用更简单,更容易与 UITableView 一起使用。其使用步骤如下:
- 添加 UISearchController 到 ViewController 中:
// Search Bar things
// self.searchController 定义是一个 UISearchController 属性
_searchController = [[UISearchController alloc]initWithSearchResultsController:nil];// 我们准备在本 VC 中显示搜索结果,不用在另外的 VC 中显示结果,因此 searchResultsController 设为 nil
_searchController.searchBar.delegate = self;
_searchController.searchResultsUpdater = self;
_searchController.hidesNavigationBarDuringPresentation = YES;// 激活 searchController 时隐藏导航栏
_searchController.dimsBackgroundDuringPresentation = false;// 因为我们使用当前视图显示搜索结果,因此没必要在显示结果时将 view 调暗。
self.definesPresentationContext = true;//当用户导航至其他 VC 且 UISearchController 为 active 时,不需要显示 search bar。
[_searchController.searchBar sizeToFit];// 否则 searchBar 不显示。此句需放在下一句前,否则会遮掉表格第一行
_tableView.tableHeaderView = _searchController.searchBar;// search bar 置于表头
- 实现 UISearchResultsUpdating 协议
首先声明 ViewController 实现 UISearchResultsUpdating 协议和 UISearchBarDelegate 协议
。然后实现如下方法:
#pragma mark - UISearchResultsUpdating
- (void)updateSearchResultsForSearchController:(UISearchController *)searchController{
NSString* searchText = searchController.searchBar.text;
if (searchText == nil) {
// If empty the search results are the same as the original data
_searchResult = _contacts;
} else {
[_searchResult removeAllObjects];
for (APContact *contact in _contacts) {
NSString* name = contact.name.compositeName;
NSString* number = contact.phones[0].number;
if ([number containsString:searchText] || [[name lowercaseString] containsString:[searchText lowercaseString]]) {
[_searchResult addObject:contact];
}
}
}
[_tableView reloadData];
}
#pragma mark - UISearchBarDelegate
- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar {
searchBar.text = nil;
[searchBar resignFirstResponder];
[_tableView reloadData];
}
- 修改 numberOfRowsInSection 方法:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (_searchController.active && !isEmpty(_searchController.searchBar.text)) {
return _searchResult.count;
}
return self.contacts.count;
}
其中,contacts 是完整列表,定义为 NSMutableArray 属性。searchResult 是搜索结果列表,定义为 NSArray 属性。
- 修改 cellForRowAtIndexPath 方法:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *CellIdentifier = @"ContactCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue2
reuseIdentifier:CellIdentifier];
}
APContact* person=nil;
if(_searchController.active && !isEmpty(_searchController.searchBar.text)) {
person = self.searchResult[indexPath.row];
} else {
person = self.contacts[indexPath.row];
}
cell.textLabel.text = person.name.compositeName;
cell.detailTextLabel.text = person.phones[0].number;
if ([self peopleIsSelected:person]) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}else{
cell.accessoryType = UITableViewCellAccessoryNone;
}
return cell;
}
- 如果有必要,还需要实现 didSelectRowAtIndexPath 方法:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:[tableView indexPathForSelectedRow] animated:NO];
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
APContact* person=nil;
if(_searchController.active && !isEmpty(_searchController.searchBar.text)) {
person = self.searchResult[indexPath.row];
} else {
person = self.contacts[indexPath.row];
}
if ([self peopleIsSelected:person]) {
cell.accessoryType = UITableViewCellAccessoryNone;
[self removeSelectedPeople:person];
}else{
cell.accessoryType = UITableViewCellAccessoryCheckmark;
[self.selectedPeople addObject:person];
}
}
51.如何修改 SearchBar 的背景色?
searchBar.barTintColor = [UIColor redColor];
返回目录
52.当TableView 滚动后,直到 searBar 不可见,如何再次让 searchBar 可见?
用方法把 searchBar 滚动回来:
[_tableView setContentOffset:CGPointMake(0, 0) animated:YES];
或者(如果 ViewController 有导航栏显示的话):
// 64 = 状态栏高度+导航栏高度
[_tableView setContentOffset:CGPointMake(0, -64) animated:YES];
注意,千万不能用
[searchBar becomeFirstResponder];
或者 :
_searchController.active = YES;
这样会导致 searchBar 消失不见!
返回目录
53. 如何以动画方式改变UISearchBar 的 barTintColor?
barTintColor 是一个比较特殊的属性,无法以 UIView 动画或 CA 动画的方式使其改变。我们只能用一个循环自己计算 barTintColor 在一定时间内每个渐变颜色值,并在一定时间内循环应用这些颜色值来形成动画:
void blinkSearchBar(UISearchBar* searchBar ,UIColor* distinctColor,NSTimeInterval seconds){
UIColor* oldColor = searchBar.barTintColor == nil ? [UIColor colorWithHex:0xbdbdc3 alpha:1] : searchBar.barTintColor;
// 去除下方细线
searchBar.layer.borderWidth = 1;
searchBar.layer.borderColor = [[UIColor lightGrayColor] CGColor];
double rate = 20;
for (int i=0; i<rate; i++) {
if(i == rate-1){// 最后一次过渡
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(i*(1/rate)*seconds * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
searchBar.barTintColor = oldColor;
});
}else{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(i*(1/rate)*seconds * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 根据 alpha 融合 distinctColor 和 oldColor,oldColor 所占比例会逐渐增大直至 1,distinctColor 所占比例逐渐减少直至 0
UIColor* blendColor = [distinctColor alphaBlendWithColor:oldColor alpha:i*(1/rate)];
searchBar.barTintColor = blendColor;
});
}
}
}
其中,alphaBlendWithColor 方法用于计算渐变色(通过按 alpha 比例逐步减少初值、增加终值来融合新颜色),它的定义如下:
@implementation UIColor (AlphaBlend)
- (UIColor *)alphaBlendWithColor:(UIColor *)blendColor alpha:(CGFloat)alpha
{
CGFloat redComponent;
CGFloat greenComponent;
CGFloat blueComponent;
[self getRed:&redComponent green:&greenComponent blue:&blueComponent alpha:nil];
CGFloat blendColorRedComponent;
CGFloat blendColorGreenComponent;
CGFloat blendColorBlueComponent;
[blendColor getRed:&blendColorRedComponent green:&blendColorGreenComponent blue:&blendColorBlueComponent alpha:nil];
CGFloat blendedRedComponent = ((blendColorRedComponent - redComponent) * alpha + redComponent);
CGFloat blendedGreenComponent = ((blendColorGreenComponent - greenComponent) * alpha + greenComponent);
CGFloat blendedBlueComponent = ((blendColorBlueComponent - blueComponent) * alpha + blueComponent);
return [UIColor colorWithRed:blendedRedComponent green:blendedGreenComponent blue:blendedBlueComponent alpha:1.0f];
}
@end
54. 如何去掉 UISearchBar 下方的细线?
UISearchBar 下方细线如下图所示:
使用如下代码去除它:
sBar.layer.borderWidth = 1;
sBar.layer.borderColor = [[UIColor lightGrayColor] CGColor];
55. 如何传递参数给一个 Container View Controller?
首先,在故事板中,将 Container View 和子 View Controller 之间的 segue 指定一个 Identifier,比如 “embed_controller”。然后在这个 View Controller(父 VC)中实现 prepareForSegue 方法:
if segue.identifier == "emded_controller"{
let vc = segue.destinationViewController as! SafetyVerificationEmbedController
vc.phoneCode = phoneCode // 将 phoneCode 传递给子 View Controller
}