iOS 开发问与答(39-55)

目录

39. 定制 cell 的 Edit View

40. iOS 9 中添加 http 请求白名单

41.当收到推送通知时, didReceiveRemoteNotification 方法不调用

42.接收到推送通知时,如何更新 Badge 数?

43.为什么收到推送通知时,不会播放声音?

44.如何在消息载体中指定自定义的声音?

45.如何读取系统声音?

46. 如何将中文转换为拼音?

47. UITextView 在 iOS 7 上文字不能顶部对齐。

48.在数组中删除对象时出错:“Collection was mutated while being enumerated”

49. 当我修改 cell 背景色时,AccessoryView 的背景色仍然是原来的。如何让 AccessoryView 的背景色也做同样改变?

50. 如何使用 UISearchController

51.如何修改 SearchBar 的背景色?

52.当TableView 滚动后,直到 searBar 不可见,如何再次让 searchBar 可见?

53. 如何以动画方式改变UISearchBar 的 barTintColor?

54. 如何去掉 UISearchBar 下方的细线?

55. 如何传递参数给一个 Container View Controller?

39. 定制 cell 的 Edit View

  1. 实现数据源方法 canEditRowAtIndexPath ,并根据 IndexPath 返回一个 Bool 值——YES 表示支持滑动操作,NO 表示不支持。
  2. 实现数据源方法 commitEditingStyle 。空实现,不需要编写任何代码。
  3. 实现委托方法 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 处于后台时就调用这些方法,需要使用“静默的推送通知”:

  1. 打开后台运行选项中的 Remote notifications:

  2. 在通知载体中添加 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 一起使用。其使用步骤如下:

  1. 添加 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 置于表头
  1. 实现 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];
}
  1. 修改 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 属性。

  1. 修改 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;
}
  1. 如果有必要,还需要实现 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
 }

返回目录

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值