ios9变化挺多的,一哥们写的挺好,贴上地址。
https://github.com/ChenYilong/iOS9AdaptationTips
一、ios9网络适配_ATS(App Transport Security):改用更安全的HTTPS
一个符合 ATS 要求的 HTTPS,应该满足如下条件:
1、Transport Layer Security协议版本要求TLS1.2以上
2、服务的Ciphers配置要求支持Forward Secrecy等
3、证书签名算法符合ATS要求等
解决方案
方案一:立即让公司的服务端升级使用TLS 1.2,以解析相关数据
方案二:虽Apple不建议,但可通过在 Info.plist 中声明,倒退回不安全的网络请求依然能让App访问指定http,甚至任意的http
让ios9支持http的最暴力的方法:
在info.plist中设置:NSAppTransportSecurity( 类型Dictionary)->Allow Arbitrary Loads(类型Boolean):YES
在OS X EI Capitan系统的终端中通过nscurl命令来诊断检查你的HTTPS服务配置是否满足Apple的ATS要求:$ nscurl --verbose --ats-diagnostics https://
二、iOS9新特性:更灵活的后台定位
#import "ViewController.h"
#import "WGS84TOGCJ02.h"
#import "AFNetworking.h"
#import
static const CLLocationDegrees EmptyLocation=-1000.0;
@interface ViewController () <CLLocationManagerDelegate,UIAlertViewDelegate>
@property(nonatomic,retain) CLLocationManager *kLocationmanager;
@property(nonatomic,strong) CLLocation *kLocation;
/*地理编码器*/
@property(nonatomic,retain) CLGeocoder *kGeocdoer;
@end
@implementation ViewController
#pragma mark - ♻️ Life Cycle
- (void)viewDidLoad {
[super viewDidLoad];
[self uiConfig];
//判断当前定位服务是否可用
if (![CLLocationManager locationServicesEnabled]) {
[self openGPSLocationTips];
}
//KVO KVC监测self.kLocaton值的变化
[self addObserverOfLocation];
[self startLocationUser];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
//-(void)dealloc{
// //KVO反注册
// [self removeObserver:self forKeyPath:@"kLocation" context:nil];
//}
#pragma mark - �� CLLocationManagerDelegate
/*更新用户位置,会频繁调用*/
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations{
CLLocation *location=[locations objectAtIndex:0];
//判断是不是属于国内范围
if (![WGS84TOGCJ02 isLocationOutOfChina:[location coordinate]]) {
//转换后的coord
// CLLocationCoordinate2D coordinate=[WGS84TOGCJ02 transformFromWGSToGCJ:[location coordinate]];
// self.kLocation=[[CLLocation alloc] initWithLatitude:coordinate.latitude longitude:coordinate.longitude];
self.kLocation=[[CLLocation alloc] initWithLatitude:location.coordinate.latitude longitude:location.coordinate.longitude];
}
}
/* 检测应用是否开启定位服务 */
-(void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error{
[manager stopUpdatingLocation];
NSLog(@"error=%@",error.localizedDescription);
switch ([error code]) {
case kCLErrorDenied:
[self openGPSLocationTips];
break;
case kCLErrorLocationUnknown:
break;
default:
break;
}
}
#pragma mark - �� UIAlertViewDelegate
-(void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex{
if (buttonIndex==1) {
NSURL *url=[NSURL URLWithString:@"prefs:root=LOCATION_SERVICES"];
if ([[UIApplication sharedApplication] canOpenURL:url]) {
[[UIApplication sharedApplication] openURL:url];
}
}
}
#pragma mark - �� Private Method
-(void)uiConfig{
self.title=@"ios9 Location Background";
UINavigationBar *bar=self.navigationController.navigationBar;
[bar setTintColor:[UIColor whiteColor]];
[bar setTitleTextAttributes:@{NSFontAttributeName:[UIFont boldSystemFontOfSize:18],NSForegroundColorAttributeName:[UIColor whiteColor]}];
[bar setBarTintColor:[UIColor colorWithRed:(51)/255.f green:(171)/255.f blue:(160)/255.f alpha:1.f]];
}
-(void)addObserverOfLocation{
[self addObserver:self forKeyPath:@"kLocation" options:NSKeyValueObservingOptionNew context:nil];
}
-(void)openGPSLocationTips{
UIAlertView *alert=[[UIAlertView alloc] initWithTitle:@"提示" message:@"当前定位服务不可用,您可以点击设置" delegate:self cancelButtonTitle:@"确定" otherButtonTitles:@"设置", nil];
[alert show];
int delayInSeconds=2;
dispatch_time_t when=dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds*NSEC_PER_SEC));
dispatch_after(when, dispatch_get_main_queue(), ^{
[alert dismissWithClickedButtonIndex:0 animated:YES];
});
}
-(void)startLocationUser{
//1、实例化定位管理器
self.kLocationmanager=[[CLLocationManager alloc] init];
//2、设置代理
self.kLocationmanager.delegate=self;
//3、定位精度
[self.kLocationmanager setDesiredAccuracy:kCLLocationAccuracyBest];
//4、请求用户权限:分为两种:⓵只在前台开启定位 ⓶在后台也可定位
if ([[[UIDevice currentDevice] systemVersion] floatValue]>=8.0) {
//⓵只在前台开启定位
//[self.kLocationmanager requestWhenInUseAuthorization];
//⓶在后台也开启定位
[self.kLocationmanager requestAlwaysAuthorization];
//如果是上面的1、2这样的顺序,将导致bug:第一次启动程序后,系统将只请求⓵的权限,⓶的权限系统不会请求,只会在下一次启动应用时请求⓶
}
//5、iOS9新特性:将允许出现这种场景:同一app中多个location manager:一些只能在前台定位,另一些可在后台定位(并可随时禁止其后台定位)。
if ([[[UIDevice currentDevice] systemVersion] floatValue]>=9) {
self.kLocationmanager.allowsBackgroundLocationUpdates=YES;
//如果没有配置info.pist,程序会崩溃掉。有两种方法解决这个问题
//⓵在info.plist中配置 1、Required background modes(Arry)->Item0(string):App registers for location updates 2、NSLocationAlwaysUsageDescription(string)设置应用的名字
//⓶在对应 target 的 Capabilities -> Background Modes -> 开启 Location Updates
}
//更新用户位置
[self.kLocationmanager startUpdatingLocation];
}
#pragma mark - Baidu Address
-(void)getAddressWithLocation:(CLLocation *)location
{
NSString *str=@"http://api.map.baidu.com/geocoder?output=json&location=%f,%f&key=dc40f705157725fc98f1fee6a15b6e60";
NSString *urlStr=[NSString stringWithFormat:str,location.coordinate.latitude,location.coordinate.longitude];
AFHTTPRequestOperationManager *manager=[AFHTTPRequestOperationManager manager];
[manager GET:urlStr parameters:nil success:^(AFHTTPRequestOperation * _Nonnull operation, id _Nonnull responseObject) {
NSDictionary *result=responseObject[@"result"];
NSLog(@"result=%@",result);
NSString *detailInfo=[NSString stringWithFormat:@"%@",result[@"formatted_address"]];
UIAlertView *alertView=[[UIAlertView alloc] initWithTitle:result[@"city"] message:detailInfo delegate:nil cancelButtonTitle:@"确定" otherButtonTitles:nil, nil];
[alertView show];
} failure:^(AFHTTPRequestOperation * _Nonnull operation, NSError * _Nonnull error) {
NSLog(@"error=%@",str);
}];
}
#pragma mark - AppleAddress
-(void)getAddressInAppleAddressWithLocation:(CLLocation *)location{
//地址反编码调用的函数
CLGeocoder *coder=[[CLGeocoder alloc] init];
//这个方法会自动向Apple服务器发送异步请求 返回数据
[coder reverseGeocodeLocation:location completionHandler:^(NSArray *placemarks, NSError *error) {
//CLPlacemark 系统自带的地址信息的类 包含:国家,城市,省份等等信息
CLPlacemark *placeInfo=[placemarks objectAtIndex:0];
NSDictionary *addressDict=placeInfo.addressDictionary;
NSString *title = [addressDict objectForKey:@"Name"];
NSString *subTitle = [addressDict objectForKey:@"FormattedAddressLines"][0];
NSLog(@"City=%@ Country=%@ FormattedAddressLines=%@ Name=%@ State=%@ Street=%@ SubLocality=%@ Thoroughfare=%@",addressDict[@"City"],addressDict[@"Country"],[addressDict[@"FormattedAddressLines"] objectAtIndex:0],addressDict[@"Name"],addressDict[@"State"],addressDict[@"Street"],addressDict[@"SubLocality"],addressDict[@"Thoroughfare"]);
CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(location.coordinate.latitude, location.coordinate.longitude);
if(![self isCoordinateEmpty:coordinate]) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:title message:subTitle delegate:nil cancelButtonTitle:nil otherButtonTitles:nil];
[alert show];
int delayInSeconds = 1;
dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(when, dispatch_get_main_queue(), ^{
[alert dismissWithClickedButtonIndex:0 animated:YES];
});
}
}];
}
- (BOOL)isCoordinateEmpty:(CLLocationCoordinate2D)regionCenter {
BOOL isCoordinateEmpty = NO;
if((regionCenter.latitude == EmptyLocation)&&(regionCenter.longitude == EmptyLocation)) {
isCoordinateEmpty = YES;
}
return isCoordinateEmpty;
}
#pragma mark - �� Action Method
/*KVC 当检测的kLocaton的值发生变化时会调用该方法*/
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context{
if ([keyPath isEqualToString:@"kLocation"]) {
CLLocation *location=change[NSKeyValueChangeNewKey];
if ([self.kGeocdoer isGeocoding]) {
[self.kGeocdoer cancelGeocode];
}
// [self getAddressWithLocation:location];
[self getAddressInAppleAddressWithLocation:location];
}
}
#pragma mark - �� LazyLoad
-(CLLocation *)kLocation{
if (_kLocation==nil) {
_kLocation=[[CLLocation alloc] initWithLatitude:EmptyLocation longitude:EmptyLocation];
}
return _kLocation;
}
-(CLGeocoder *)kGeocdoer{
if (_kGeocdoer==nil) {
_kGeocdoer=[[CLGeocoder alloc] init];
}
return _kGeocdoer;
}
@end
三、企业级分发
*有两处变化:
1、iOS9以后,企业级分发ipa包将遭到与Mac上dmg安装包一样的待遇:默认不能安装,也不再出现“信任按钮”
2、iOS9以后,企业分发时可能存在:下载的ipa包与网页两者的 bundle ID 无法匹配而导致下载失败的情况*
Q-A
Q:企业分发,企业版证书在iOS9上安装应用报 Ignore manifest download, already have bundleID: com.mycom.MyApp 只有我的手机无法安装,别人 iOS9 都可以安装
A:这并非 iOS9的问题,iOS8及以前的系统也会出现,和缓存有关系,请尝试关机重启手机,然后就可以安装了
四、ios9 URL Scheme适配_引入白名单概念
在iOS9中,如果使用 canOpenURL: 方法,该方法所涉及到的 URL scheme 必须在”Info.plist”中将它们列为白名单,否则不能使用。key叫做LSApplicationQueriesSchemes ,键值内容是
LSApplicationQueriesSchemes
urlscheme
urlscheme2
urlscheme3
urlscheme4
白名单上限是50个:
五、搜索API
分为两类首先都要导入CoreSpotlight.framework、MobileCoreServies.framework.
第一种:
#import
#import
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self uiConfig];
[self spotLightIndexing];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
-(void)uiConfig{
self.view.backgroundColor=[UIColor whiteColor];
self.title=@"ios9ApiDemo";
}
-(void)spotLightIndexing{
NSString *path=[[NSBundle mainBundle] pathForResource:@"data" ofType:@"plist"];
NSArray *plistArr=[[NSArray alloc] initWithContentsOfFile:path];
[plistArr enumerateObjectsUsingBlock:^(NSDictionary * _Nonnull dict, NSUInteger idx, BOOL * _Nonnull stop) {
CSSearchableItemAttributeSet *attributeSet = [[CSSearchableItemAttributeSet alloc] initWithItemContentType:(NSString *)kUTTypeImage];
// Set properties that describe attributes of the item such as title, description, and image.
NSString *title = [dict objectForKey:@"title"];
attributeSet.title = title;
attributeSet.contentDescription = [NSString stringWithFormat:@"%@,天空是什么颜色,听阴天说什么",title];
attributeSet.keywords = @[title];
// Create an attribute set for an item
UIImage *image = [UIImage imageNamed:[dict objectForKey:@"image_name"]];
NSData *imageData = [NSData dataWithData:UIImagePNGRepresentation(image)];
attributeSet.thumbnailData = imageData;
// Create a searchable item, specifying its ID, associated domain, and the attribute set you created earlier.
CSSearchableItem *item;
NSString *identifier = [NSString stringWithFormat:@"%@",attributeSet.title];
item = [[CSSearchableItem alloc] initWithUniqueIdentifier:identifier domainIdentifier:@"what's up" attributeSet:attributeSet];
// Index the item.
[[CSSearchableIndex defaultSearchableIndex] indexSearchableItems:@[item] completionHandler: ^(NSError * __nullable error) {
if (error) {
NSLog(@"error=%@",error.localizedDescription);
}
}];
}];
}
@end
第二种:
数据模型:
#import
@interface PersonModel : NSObject
@property(nonatomic,copy) NSString *pName,*pId,*pImageName;
@end
ViewController:
#import
@interface ViewController : UIViewController
-(void)showDetailViewControllerWithPId:(NSString *)pId;
@end
#import "ViewController.h"
#import "PersonModel.h"
#import "DetailViewController.h"
#import
#import
@interface ViewController () <UITableViewDataSource,UITableViewDelegate>
@property(nonatomic,retain) NSMutableArray *kDataSource;
@property(nonatomic,retain) UITableView *kTableView;
@end
@implementation ViewController
#pragma mark - Life Cycle
- (void)viewDidLoad {
[super viewDidLoad];
[self loadDataResource];
[self uiConfig];
[self savePeopleToIndex];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
#pragma mark - Public Method
-(void)showDetailViewControllerWithPId:(NSString *)pId{
for (PersonModel *person in self.kDataSource) {
if ([person.pId isEqualToString:pId]) {
DetailViewController *VC=[[DetailViewController alloc] initWithName:person.pName andImageName:person.pImageName];
[self.navigationController pushViewController:VC animated:YES];
}
}
}
#pragma mark - UITableViewDataSource
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return [self.kDataSource count];
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
PersonModel *model=[self.kDataSource objectAtIndex:indexPath.row];
static NSString *cellId=@"cellId";
UITableViewCell *cell=[tableView dequeueReusableCellWithIdentifier:cellId];
if (cell==nil) {
cell=[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellId];
}
cell.textLabel.text=model.pName;
// cell.imageView.image=[UIImage imageNamed:model.pImageName];
return cell;
}
#pragma mark - UITableViewDelegate
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
PersonModel *person=[self.kDataSource objectAtIndex:indexPath.row];
DetailViewController *VC=[[DetailViewController alloc] initWithName:person.pName andImageName:person.pImageName];
[self.navigationController pushViewController:VC animated:YES];
}
#pragma mark - Private Method
-(void)uiConfig{
self.edgesForExtendedLayout=UIRectEdgeNone;
self.view.backgroundColor=[UIColor whiteColor];
[self.view addSubview:self.kTableView];
}
-(void)loadDataResource{
NSArray *personArr=@[@"ben",@"jane",@"pete",@"ray",@"tom"];
for (int i=0; i<[personArr count]; i++) {
PersonModel *person=[[PersonModel alloc] init];
person.pId=[NSString stringWithFormat:@"%d",i+1];
person.pName=personArr[i];
person.pImageName=personArr[i];
[self.kDataSource addObject:person];
}
}
-(void)savePeopleToIndex{
NSMutableArray *searchableItems=[NSMutableArray array];
for (PersonModel *person in self.kDataSource) {
CSSearchableItemAttributeSet *attributedSet=[[CSSearchableItemAttributeSet alloc] initWithItemContentType:@"image"];
attributedSet.title=person.pName;
attributedSet.contentDescription=[NSString stringWithFormat:@"你好,我是%@",person.pName];
UIImage *avatarImg=[UIImage imageNamed:person.pImageName];
attributedSet.thumbnailData=UIImagePNGRepresentation(avatarImg);
CSSearchableItem *item=[[CSSearchableItem alloc] initWithUniqueIdentifier:person.pId domainIdentifier:@"com.company.demo" attributeSet:attributedSet];
[searchableItems addObject:item];
}
[[CSSearchableIndex defaultSearchableIndex] indexSearchableItems:searchableItems completionHandler:^(NSError * _Nullable error) {
if (error) {
NSLog(@"error=%@",error.localizedDescription);
}
}];
}
#pragma mark - Lazy Method
-(UITableView *)kTableView{
if (_kTableView==nil) {
_kTableView=[[UITableView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height-64) style:UITableViewStylePlain];
_kTableView.dataSource=self;
_kTableView.delegate=self;
_kTableView.tableFooterView=[UIView new];
_kTableView.rowHeight=44;
}
return _kTableView;
}
-(NSMutableArray *)kDataSource{
if (_kDataSource==nil) {
_kDataSource=[[NSMutableArray alloc] init];
}
return _kDataSource;
}
@end
DetailViewController:
#import
@interface DetailViewController : UIViewController
-(instancetype)initWithName:(NSString *)nameStr andImageName:(NSString *)imgName;
@end
#import "DetailViewController.h"
@interface DetailViewController ()
@property(nonatomic,copy) NSString *kName,*kImgName;
@property(nonatomic,retain) UIImageView *kImgView;
@end
@implementation DetailViewController
#pragma mark - Public Method
-(instancetype)initWithName:(NSString *)nameStr andImageName:(NSString *)imgName{
self=[super init];
if (self) {
self.kName=nameStr;
self.kImgName=imgName;
}
return self;
}
#pragma mark - Life Cycle
- (void)viewDidLoad {
[super viewDidLoad];
[self uiConfig];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
#pragma mark - Private Method
-(void)uiConfig{
self.view.backgroundColor=[UIColor whiteColor];
self.title=self.kName;
[self.view addSubview:self.kImgView];
}
#pragma mark - Lazy Method
-(UIImageView *)kImgView{
if (_kImgView==nil) {
_kImgView=[[UIImageView alloc] initWithFrame:CGRectMake((self.view.frame.size.width-100)/2, (self.view.frame.size.height-100)/2, 100, 100)];
_kImgView.image=[UIImage imageNamed:self.kImgName];
}
return _kImgView;
}
@end
AppDelegate:
#import "AppDelegate.h"
#import "ViewController.h"
@interface AppDelegate ()
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
ViewController *VC=[[ViewController alloc] init];
UINavigationController *navi=[[UINavigationController alloc] initWithRootViewController:VC];
self.window.rootViewController=navi;
return YES;
}
/*当spotlight搜索到的时候会调用这个方法*/
-(BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray * _Nullable))restorationHandler{
NSString *frinedId=userActivity.userInfo[@"kCSSearchableItemActivityIdentifier"];
UINavigationController *navi=(UINavigationController*)self.window.rootViewController;
[navi popToRootViewControllerAnimated:NO];
ViewController *VC=[navi.viewControllers firstObject];
[VC showDetailViewControllerWithPId:frinedId];
return YES;
}
@end
六、iOS国际化问题:当前设备语言字符串返回有变化
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSArray *allLanguage = [defaults objectForKey:@"AppleLanguages"];
NSString *currentLanguage = [allLanguage objectAtIndex:0];
NSLog(@"The current language is : %@", currentLanguage);
iOS 9 之前:以上返回结果:语言字符串代码。例如:"zh-Hans"
iOS 9:以上返回结果:语言字符串代码 + 地区代码。例如:"zh-Hans-US"