app ios 唤起 无效网页
2017-04-19 10:33:05 YLGWHYH 阅读数 1420

转载至:https://www.jianshu.com/p/a74dc1d90503
 

iOS是一个封闭的系统,应用之间是不可以互相读取文件的。

实现途径:
URL Scheme是苹果为方便app之间互相调用而设计的。

  • 你可以通过一个类似URL的链接,通过系统的OpenURL来唤起该App,并可以传递一些参数。
  • 要求:每个URL必须能唯一标识一个App,如果你设置的URL与别的APP的URL冲突,此时,你的APP不一定会被调用起来。

例如:下列常用URL Scheme

//微信URLscheme
@"weixin://app/%@/pay/?nonceStr=%@&package=Sign%%3DWXPay&partnerId=%@&prepayId=%@&timeStamp=%@&sign=%@&signType=SHA1"

//高德地图
@"iosamap://path?sourceApplication=ApplicationName&sid=BGVIS1&slat=%f&slon=%f&sname=当前位置&did=BGVIS2&dlat=%f&dlon=%f&dname=%@&dev=0&m=0&t=0"

//百度地图
@"baidumap://map/direction?origin=%f,%f&destination=latlng:%f,%f|name=%@&mode=driving&coord_type=gcj02"

//腾讯地图
@"qqmap://map/routeplan?from=当前位置&fromcoord=%f,%f&type=drive&tocoord=%f,%f&to=%@&coord_type=1&policy=0"



[[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"http://www.baidu.com"]];  //唤起浏览器 打开网页
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"sms://158********"]];  //唤起发送信心功能
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"tel://158********"]];  //唤起拔打电话功能
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"mailto://362****@qq.com"]];  //唤起邮件功能


实例教学: App1 唤起 App2
创建两个app: App1唤起方 App2接收方

APP2_接收方需要做的事情:

(1)设置URL Scheme

  1. 第一种方法:info.plist

    • URL Schemes:唯一性

    • URL identifier:可以是任何值,但建议用“反域名”(例如 “com.fcplayer.testHello”)

      配置URL Schemes_infoplist.png

  1. 第二种方法: Target—>Info—>URL Types

    • URL Schemes:唯一性

    • identifier:可以是任何值,但建议用“反域名”(例如 “com.fcplayer.testHello”)

      配置URL Schemes_info.png

(2) 在AppDelegate.m里面增加回调方法

#pragma mark --回调方法
/// iOS 9.0+
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options
{
    // Keys for application:openURL:options:
    UIApplicationOpenURLOptionsSourceApplicationKey // value is an NSString containing the bundle ID of the originating application
    UIApplicationOpenURLOptionsAnnotationKey // value is a property-list typed object corresponding to what the originating application passed in UIDocumentInteractionController's annotation property
    UIApplicationOpenURLOptionsOpenInPlaceKey  // value is a bool NSNumber, set to YES if the file needs to be copied before use

}


/// iOS 2.0–9.0
- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url
{
    NSLog(@"URL scheme:%@", [url scheme]);
    NSLog(@"URL query: %@", [url query]);
}


/// iOS 4.2–9.0
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{
    //通过sourceApplication来判断来自哪个app以决定要不要唤醒自己的app
    if ([sourceApplication isEqualToString:@"Bundle identifier"])
    {
        NSLog(@"%@", sourceApplication);    //来源于哪个app(Bundle identifier)
        NSLog(@"scheme:%@", [url scheme]);  //url scheme
        NSLog(@"query: %@", [url query]);   //可以通过[url query]来获得查询串  (传递参数)
        return YES;
    }
    else{
        return NO;
    }
}

APP1_唤起方需要做的事情:

1.唤起方法:

唤起方直接在点击事件里调用唤起方法:

/// 三个方法:

/// iOS 3.0+
- (BOOL)canOpenURL:(NSURL *)url;

/// iOS 10.0+
- (void)openURL:(NSURL *)url options:(NSDictionary<NSString *,id> *)options completionHandler:(void (^)(BOOL success))completion;

/// iOS 2.0–10.0
- (BOOL)openURL:(NSURL *)url;



//e.g. 唤起第三方App
- (void)evokeOtherApp {
    //app2是应用的唯一的scheme
    //scheme 必须是“app2://lanch” “weixin://app”类似的格式:
    NSURL *url = [NSURL URLWithString:@"app2://lanch?key=param"];

    if ([[UIApplication sharedApplication] canOpenURL:url]) {


        if ([[UIApplication sharedApplication] respondsToSelector:@selector(openURL:options:completionHandler:)]) {
            //iOS 10.0+
            [[UIApplication sharedApplication] openURL:url options:@{} completionHandler:^(BOOL success) {
            }];
        }else{
            //iOS 2~10
            [[UIApplication sharedApplication] openURL:url];
        }
    }
    else
    {
        //一般是没有安装
        NSLog(@"跳转下载app链接");
    }
}

2.sheme设置

如果你是iOS 9.0以上的系统,有时候会报错:

2017-01-05 11:06:24.343 APP1[235:48284] -canOpenURL: failed for URL: "app2://lanch?key=param" - error: "This app is not allowed to query for scheme app2"

因为:iOS 9.0以上的系统需要在“Info.plist”中将要使用的URL Schemes列为白名单,才可正常检查唤起的第三方应用是否安装。受此影响,当你的应用在iOS 9中需要使用 QQ/QQ空间/支付宝/微信SDK 的相关能力时,需要在“Info.plist”里增加白名单:

key>LSApplicationQueriesSchemes</key>
 <array>
    <!-- 微信 URL Scheme 白名单-->
    <string>wechat</string>
    <string>weixin</string>

    <!-- 新浪微博 URL Scheme 白名单-->
    <string>sinaweibohd</string>
    <string>sinaweibo</string>
    <string>sinaweibosso</string>
    <string>weibosdk</string>
    <string>weibosdk2.5</string>

    <!-- QQ、Qzone URL Scheme 白名单-->
    <string>mqqapi</string>
    <string>mqq</string>
    <string>mqqOpensdkSSoLogin</string>
    <string>mqqconnect</string>
    <string>mqqopensdkdataline</string>
    <string>mqqopensdkgrouptribeshare</string>
    <string>mqqopensdkfriend</string>
    <string>mqqopensdkapi</string>
    <string>mqqopensdkapiV2</string>
    <string>mqqopensdkapiV3</string>
    <string>mqzoneopensdk</string>
    <string>wtloginmqq</string>
    <string>wtloginmqq2</string>
    <string>mqqwpa</string>
    <string>mqzone</string>
    <string>mqzonev2</string>
    <string>mqzoneshare</string>
    <string>wtloginqzone</string>
    <string>mqzonewx</string>
    <string>mqzoneopensdkapiV2</string>
    <string>mqzoneopensdkapi19</string>
    <string>mqzoneopensdkapi</string>
    <string>mqzoneopensdk</string>

    <!-- 支付宝  URL Scheme 白名单-->
    <string>alipay</string>
    <string>alipayshare</string>

</array>

解决方案:

(1)在Info.plist里,配置支持http协议:

    <key>NSAppTransportSecurity</key>
    <dict>
        <key>NSAllowsArbitraryLoads</key>
        <true/>
    </dict>
`

(2)在Info.plist里,配置scheme到LSApplicationQueriesSchemes,也就是白名单:

<key>LSApplicationQueriesSchemes</key>
    <array>
        <string>app2</string>
        <string>app3</string>
        <string>app4</string>
    </array>

(3)延伸...?

// 代码动态把key加入白名单(不可行)
            var info = Bundle.main.infoDictionary
            guard var array:[Any] = info?["LSApplicationQueriesSchemes"] as? [Any] else {return}
            array.append(iosKey)
            print(array)
            var info2 = Bundle.main.infoDictionary
            print(info2)

跳转app_sheme设置.png

3.没有安装的处理

控制台输出错误:

2017-01-05 11:22:30.741 APP1[252:51626] -canOpenURL: failed for URL: "app3://lanch?key=param" - error: "(null)"
2017-01-05 11:22:30.746 APP1[252:51626] -canOpenURL: failed for URL: "app3://lanch?key=param" - error: "(null)"
2017-01-05 11:22:30.746 APP1[252:51626] [[UIApplication sharedApplication] canOpenURL:url] = 0

一般是没有安装App,可以跳转下载链接。

Demo下载地址:
链接: https://pan.baidu.com/s/1kViBtS7 密码: kvid

或者:https://github.com/mrhyh/arouse

2017-12-13 10:32:30 mingmingsuper 阅读数 110
现在的生活中大家经常用到智能手机,其中很大部分用的是苹果的手机。在生活中经常会有这样的场景,
在一个应用内会有分享或者需要支付的需求,通常会通过当前APP调用第三方的支付或者分享,
例如微信支付、微信分享、微博分享等。那么这些功能到底如何在iOS设备上做到的呢,
那就是通过开发通过URL Scheme的定义实现的,APP通过外部调用从而调用相应的功能实现。
查看原文:https://www.liuandy.cn/informal_essay/2017/12/13/2134.html
2013-09-18 18:08:14 whf727 阅读数 4885

有些时候我们需要再其他地方把app唤起,并打开跳转到指定的vc上面。这里我自己写了一个vc的mgr,最主要的技术是method swizzle。原理就不详述,看代码吧。


//
//  ViewControllerMgr.h
//  
//
//  Created by Tommy on 13-8-14.
//  Copyright (c) 2013年 Tommy. All rights reserved.
//

#import <Foundation/Foundation.h>


@protocol ViewControllerMgrDelegate <NSObject>



- (BOOL) willCreateVC:(NSURL*)url;
- (BOOL) willPresentVC:(UIViewController*)onVc currentVC:(UIViewController*) presentVC url:(NSURL*)url;


//if return no, will not dispatch delay url
- (BOOL) willDispatchDelayedUrl:(NSURL*)url;
//- (BOOL) needchangeToNextVC:(UIViewController*)onVc;


@optional
//if return no, will not set the param by vcmgr
//please set param by yourself in delegate, and return no
- (BOOL) willSetParamter:(UIViewController*)onVc key:(NSString*)key value:(NSString*)value;

@optional

- (UIViewController*) creatViewController:(NSString*)vcKey paramters:(NSDictionary*)parameters;

@end

#define  dispatch_delayed_notification_name @"_dispatchDelayedViewControllers"

@interface ViewControllerMgr : NSObject

@property(weak) id<ViewControllerMgrDelegate> delegate;

+(id) sharedInstance;

//如果当前的vc刚好和需要显示的vc是同一个类,如果不需要再这个之上弹出,而只是修改当前vc的内容,请设置为YES,否则为NO
//默认为NO
@property (assign) BOOL enablePresentOnSameVC;
@property (strong) NSString * scheme;
//保持需要被推迟的vc 的url
@property (strong) NSMutableArray * delayedUrlArray;

- (BOOL) handleUrl:(NSURL*)url;


- (void) registerViewController:(NSString*)key  ClassName:(NSString*)vcName;
- (void) registerViewController:(NSString*)key  Class:(Class) vcClass;
- (void) registerViewController:(NSDictionary*)dic;

//register vc init paramters
- (void) registerInitParameters:(NSArray*) array ClassName:(NSString*)vcName;
- (void) registerVCWithClassName:(NSString*)vcName;
- (void) registerVCInitWithCoder:(NSCoder *)aDecoder ClassName:(NSString*)vcName;
- (void) registerVCInitWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil ClassName:(NSString*)vcName;


//delay
- (void) addToDelay:(NSURL*)url;
//call by
- (void) dispatchDelayedViewControllers;
- (void) addViewControllerToDispatchQueue:(UIViewController*)vc;

//暂时不支持
//- (void) presentViewController:(NSString*)key;
//- (void) presentModalViewController:(NSString*)key paramters:(NSString*)paramters;

- (void) presentModalViewController:(NSURL*)url;

- (UIView*) topView;
- (UIViewController*) topViewController;




@end



//
//  ViewControllerMgr.m
// 
//
//  Created by Tommy on 13-8-14.
//  Copyright (c) 2013年 Tommy. All rights reserved.
//

#import "ViewControllerMgr.h"
#import <objc/runtime.h>
#import <objc/objc.h>


//static TomStack* s_vcStack = nil;
static NSMutableDictionary* s_vcInitParametersDic = nil;

#pragma mark -
#pragma mark implement BaseViewController
UIViewController * g_lastViewController = nil;


#pragma mark -
#pragma mark implement ViewControllerMgr
static ViewControllerMgr* s_vcmgr = nil;
@implementation ViewControllerMgr
{
    NSMutableDictionary* vcDic;
    
    BOOL dispatchOpened;
}

- (id) init
{
    if(self =[super init])
    {
        vcDic = [NSMutableDictionary new];
        _enablePresentOnSameVC = NO;
        dispatchOpened = NO;
        
        [self installHook];
    }
    
    return self;
}

+(id) sharedInstance
{
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        if (s_vcmgr == nil)
        {
            s_vcmgr = [[self alloc] init]; //autorelease];
            [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(dispatchDelayedViewControllers) name:dispatch_delayed_notification_name object:nil];
        }
    });

    return s_vcmgr;
}

+(id) allocWithZone:(NSZone *)zone
{
    @synchronized(self)
    {
        if (s_vcmgr == nil)
        {
            s_vcmgr = [super allocWithZone:zone];
            return s_vcmgr;
        }
    }   
    return nil; 
}

- (BOOL) handleUrl:(NSURL*)url 
{
    NSAssert(_scheme,@"scheme is null");
    NSAssert(_delegate,@"delegate is null");
    
    @try {
        if(url && _scheme && [_scheme isEqualToString:[url scheme]])
        {
            [[ViewControllerMgr sharedInstance] presentModalViewController:url];
            return YES;
        }
    }
    @catch (NSException *exception) {
        NSLog(@"严重错误!!!!!");
    }
    
    return NO;
}

//register vc
-(void) registerViewController:(NSString*)key  ClassName:(NSString*)vcName
{
    [self registerViewController:key  Class:NSClassFromString(vcName)];
}
-(void) registerViewController:(NSString*)key  Class:(Class) vcClass
{
    //if([vcClass isKindOfClass:[UIViewController class]])
    [vcDic setObject:vcClass forKey:key];
}
- (void) registerViewController:(NSDictionary*)dic
{
    for(id obj in dic)
    {
        [self registerViewController:obj ClassName:[dic valueForKey:obj]];
    }
}

//register
#pragma mark -
#pragma mark register vc init paramters
- (void) registerInitParameters:(NSArray*) array ClassName:(NSString*)vcName
{
    if(!s_vcInitParametersDic)
    {
        s_vcInitParametersDic = [NSMutableDictionary new];
    }
    
    [s_vcInitParametersDic setValue:array forKey:vcName];
}

- (void) registerVCWithClassName:(NSString*)vcName
{
    [self registerInitParameters:@[[NSNull null]] ClassName:vcName];
   
}
- (void) registerVCInitWithCoder:(NSCoder *)aDecoder ClassName:(NSString*)vcName
{
    [self registerInitParameters:@[aDecoder?aDecoder:[NSNull null],[NSNull null]] ClassName:vcName];
}
- (void) registerVCInitWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil ClassName:(NSString*)vcName
{
    [self registerInitParameters:@[nibNameOrNil?nibNameOrNil:[NSNull null],nibBundleOrNil?nibBundleOrNil:[NSNull null],[NSNull null]] ClassName:vcName];
}

//presetn vc
- (NSDictionary*) parseURIQueryString:(NSString*)query
{
    NSMutableDictionary* param = [[NSMutableDictionary alloc] initWithCapacity:2];
    NSArray* array = [query componentsSeparatedByString:@"&"];
    for(NSString* ss in array)
    {
         NSArray* key = [ss componentsSeparatedByString:@"="];
        
        switch ([key count]) {
            case 1:
                [param setValue:@"" forKey:[key objectAtIndex:0]];
                break;
            case 2:
                [param setValue:[key objectAtIndex:1] forKey:[key objectAtIndex:0]];
                break;
            default:
                break;
        }
    }
    return param;
}
- (UIViewController*) createViewController:(NSString*) key parameters:(NSDictionary*) paramters
{
    UIViewController* vc = nil;
    Class vcClass = [vcDic objectForKey:key];
    
    if(vcClass)
    {
        if(_enablePresentOnSameVC && g_lastViewController && [g_lastViewController isKindOfClass:vcClass])
        {
            [self setParametersForVC:g_lastViewController paramters:paramters];  
        }
        else
        {
            vc  =  [[vcClass alloc] initByVCMgr];
            [self setParametersForVC:vc paramters:paramters];
        }
        
    }
    else
    {
         NSAssert(0, @"call error %@ or %@ not inhert from BaseViewController",key,key);
    }
    
    return  vc;
}

- (void) setParametersForVC:(UIViewController*)vc paramters:(NSDictionary*) paramters
{   
    for (id  key in paramters) {
        
        @try {
            
            if(_delegate && [_delegate respondsToSelector:@selector(willSetParamter:key:value:)])
            {
               if([_delegate willSetParamter:vc key:key value:[paramters valueForKey:key]])
               {
                   [vc setValue:[paramters valueForKey:key] forKey:key];
               }
            }
            
        }
        @catch (NSException *exception) {
            NSLog(@"param invalid %@",paramters);
//            NSAssert(0, @"param invalid %@",paramters);
        }
        
    }
}




//- (void) presentViewController:(NSString*)key
//{
//    [self presentModalViewController:key paramters:nil];
//}
- (void) presentModalViewController:(NSURL*)url
{
  if([_delegate willCreateVC:url ])
  {
      NSString* path = [[url pathComponents] lastObject];
      NSString* key = path?path:[url host];
      NSDictionary* parameters = [self parseURIQueryString:[url query]];
      UIViewController* vc = nil;
      
      if([_delegate respondsToSelector:@selector(creatViewController:paramters:)])
      {
          vc = [_delegate creatViewController:key paramters:parameters];
      }
      
      if(!vc)
          vc = [self createViewController:key parameters:parameters];
    
      if(vc && g_lastViewController)
      {
          UIViewController* onVC = g_lastViewController;
                                    
          if(onVC && [_delegate willPresentVC:onVC currentVC:vc url:url] && vc != onVC)
          {
              if(onVC.navigationController)
              {
                  [onVC.navigationController pushViewController:vc animated:YES];
              }
              else
              {
                  //[vc setValue:@(YES) forKey:@"modalPresent"];
                  UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:vc];                  
                  [onVC presentModalViewController:nav animated:YES];
              }
          }

      }
  }

}

- (UIView*) topView
{
    return [[[[UIApplication sharedApplication] keyWindow] subviews] lastObject];
}

- (UIViewController*) topViewController
{
    
    return g_lastViewController;    
}

- (void) addToDelay:(NSURL*)aurl
{
    if(!_delayedUrlArray)
    {
        _delayedUrlArray = [NSMutableArray new];
    }
    
    for (NSURL* url in _delayedUrlArray) {
        if([[url absoluteString] isEqualToString:[aurl absoluteString]])
        {
            return;
        }

    }
    dispatchOpened = NO;
    [_delayedUrlArray addObject:aurl];
}

- (void) dispatchDelayedViewControllers
{
    
    dispatchOpened = YES;
    [self dispatchDelayedViewController];
}

- (void) dispatchDelayedViewController
{
    if ([_delayedUrlArray count])
    {
        NSURL * url = [_delayedUrlArray objectAtIndex:0];
        if([_delegate willDispatchDelayedUrl:url])
        {
            if ([_delayedUrlArray count] ) {
                [_delayedUrlArray removeObject:url];
                [self handleUrl:url];
            }
            
        }
    }
    
}



- (void) addViewControllerToDispatchQueue:(UIViewController*)vc
{
    
}

#pragma mark -
#pragma mark hooked method imp

//define 

#define Hooked_Orignal_Selector(_orgSelName) @selector(_vc_orignal_##_orgSelName)
#define Hooked_Method(_name) _hooked_##_name


#define Add_Method_To_Class(_class,_selName) do{    \
    Method add_method = class_getInstanceMethod([self class], @selector(_selName)); \
    IMP    add_imp = method_getImplementation(add_method);      \
    class_addMethod(_class, @selector(_selName), add_imp, method_getTypeEncoding(add_method));    \
}while(0)


#define HOOK_OBJC_CLASS(_class,_orgSelName,_hookedSelName)  do{ \
    Method org_method = class_getInstanceMethod(_class, @selector(_orgSelName));  \
    Method rep_method = class_getInstanceMethod([self class], @selector(_hookedSelName));  \
    IMP    org_imp = method_getImplementation(org_method);      \
    class_addMethod(_class, Hooked_Orignal_Selector(_orgSelName), org_imp, method_getTypeEncoding(org_method)); \
    IMP    rep_imp = method_getImplementation(rep_method); \
    class_replaceMethod(_class, @selector(_orgSelName), rep_imp, method_getTypeEncoding(org_method));   \
}while(0)


#define Set_Instance_Var(_obj,_name,_value) objc_setAssociatedObject(_obj,"_append_"#_name,_value,OBJC_ASSOCIATION_RETAIN_NONATOMIC)
#define Get_Instance_Var(_obj,_name)        objc_getAssociatedObject(_obj,"_append_"#_name)

#define REAL_SELF() UIViewController* realSelf = (UIViewController*)self


- (void) installHook
{
    
    @try {
        HOOK_OBJC_CLASS([UIViewController class],viewWillAppear:,Hooked_Method(viewDidAppearHooked:));
        HOOK_OBJC_CLASS([UIViewController class],viewDidAppear:,Hooked_Method(viewDidAppear:));
        HOOK_OBJC_CLASS([UIViewController class],presentModalViewController:animated:,Hooked_Method(presentModalViewController:animated:));
        
        Add_Method_To_Class([UIViewController class],initByVCMgr);
//        Add_Method_To_Class([UIViewController class],_modalClose:);
//        Add_Method_To_Class([UIViewController class],_addCloseBtn:);
//        Add_Method_To_Class([UIViewController class],goBack);
//        Add_Method_To_Class([UIViewController class],goHome);
        
        //class_addProperty need decalre in interface
        //class_addIvar cannot support for exist class 

    }
    @catch (NSException *exception) {
        NSLog(@"install hook occur exception");
    }
    @finally {
        
    }
   
    
}



//hooked method
//note
//self not viewcontrollermgr, is viewcontroller instance
//

-(id) initByVCMgr
{
    REAL_SELF();
    NSArray * parameters = [s_vcInitParametersDic valueForKey:[NSString stringWithUTF8String:class_getName([self class])]];
    NSAssert(parameters, @"%@ initByVCMgr failed :init parameter error",self);
    
    id  bself = nil;
    switch ([parameters count]) {
        case 1:
            bself = [realSelf init];
            break;
        case 2:
            bself = [realSelf initWithCoder:[parameters objectAtIndex:0]==[NSNull null]?nil:[parameters objectAtIndex:0]];
            break;
        case 3:
            bself = [realSelf initWithNibName:[parameters objectAtIndex:0]==[NSNull null]?nil:[parameters objectAtIndex:0] bundle:[parameters objectAtIndex:1]==[NSNull null]?nil:[parameters objectAtIndex:1]];
            break;
        default:
            NSAssert(parameters, @"%@ initByVCMgr failed:too many paramter:%@",self,parameters);
            break;
    }

    if(bself)
    {
        Set_Instance_Var(self,presentByMgr, @(YES));
    }
    
    return bself;
}

- (void) Hooked_Method(viewWillAppear:(BOOL)animated)
{
    [self performSelector:Hooked_Orignal_Selector(viewWillAppear:) withObject:@(animated)];
    if(!g_lastViewController)
    {
        g_lastViewController = (UIViewController*)self;
        [[ViewControllerMgr sharedInstance] performSelector:@selector(dispatchDelayedViewController)];
    }
}

- (void) Hooked_Method(viewDidAppearHooked:(BOOL)animated)
{
    [self performSelector:Hooked_Orignal_Selector(viewDidAppear:) withObject:@(animated)];
    UIViewController* realSelf = (UIViewController*) self;
    CGRect frame = realSelf.view.frame;
    
    if(frame.origin.x == frame.origin.y && frame.origin.x == 0)
        g_lastViewController = realSelf;
    
    [[ViewControllerMgr sharedInstance] performSelector:@selector(dispatchDelayedViewController)];
}


- (void) Hooked_Method(presentModalViewController:(UIViewController *)modalViewController animated:(BOOL)animated)
{
    if([modalViewController isKindOfClass:[UINavigationController class]])
    {
        UINavigationController * nav = (UINavigationController*)modalViewController;
        
        if([nav.viewControllers count])
        {
            Set_Instance_Var([nav topViewController],modalPresent, @(YES));
        }
    }else
    {
         Set_Instance_Var(modalViewController,modalPresent, @(YES));
    }
    
    [self performSelector:Hooked_Orignal_Selector(presentModalViewController:animated:) withObject:modalViewController withObject:@(animated)];
}


@end





2017-01-05 11:44:00 weixin_33778778 阅读数 29

iOS 唤起第三方App

iOS是一个封闭的系统,应用之间是不可以互相读取文件的。

实现途径:
URL Scheme是苹果为方便app之间互相调用而设计的。

  • 你可以通过一个类似URL的链接,通过系统的OpenURL来唤起该App,并可以传递一些参数。
  • 要求:每个URL必须能唯一标识一个App,如果你设置的URL与别的APP的URL冲突,此时,你的APP不一定会被调用起来。

例如:下列常用URL Scheme

//微信URLscheme
@"weixin://app/%@/pay/?nonceStr=%@&package=Sign%%3DWXPay&partnerId=%@&prepayId=%@&timeStamp=%@&sign=%@&signType=SHA1"

//高德地图
@"iosamap://path?sourceApplication=ApplicationName&sid=BGVIS1&slat=%f&slon=%f&sname=当前位置&did=BGVIS2&dlat=%f&dlon=%f&dname=%@&dev=0&m=0&t=0"

//百度地图
@"baidumap://map/direction?origin=%f,%f&destination=latlng:%f,%f|name=%@&mode=driving&coord_type=gcj02"

//腾讯地图
@"qqmap://map/routeplan?from=当前位置&fromcoord=%f,%f&type=drive&tocoord=%f,%f&to=%@&coord_type=1&policy=0"



[[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"http://www.baidu.com"]];  //唤起浏览器 打开网页
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"sms://158********"]];  //唤起发送信心功能
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"tel://158********"]];  //唤起拔打电话功能
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"mailto://362****@qq.com"]];  //唤起邮件功能


实例教学: App1 唤起 App2
创建两个app: App1唤起方 App2接收方

APP2_接收方需要做的事情:

(1)设置URL Scheme

  1. 第一种方法:info.plist

    • URL Schemes:唯一性

    • URL identifier:可以是任何值,但建议用“反域名”(例如 “com.fcplayer.testHello”)

      1311718-f582878326f582e5.png
      配置URL Schemes_infoplist.png
  1. 第二种方法: Target—>Info—>URL Types

    • URL Schemes:唯一性

    • identifier:可以是任何值,但建议用“反域名”(例如 “com.fcplayer.testHello”)

      1311718-e5f48ba360d5951d.png
      配置URL Schemes_info.png

(2) 在AppDelegate.m里面增加回调方法

#pragma mark --回调方法
/// iOS 9.0+
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options
{
    // Keys for application:openURL:options:
    UIApplicationOpenURLOptionsSourceApplicationKey // value is an NSString containing the bundle ID of the originating application
    UIApplicationOpenURLOptionsAnnotationKey // value is a property-list typed object corresponding to what the originating application passed in UIDocumentInteractionController's annotation property
    UIApplicationOpenURLOptionsOpenInPlaceKey  // value is a bool NSNumber, set to YES if the file needs to be copied before use

}


/// iOS 2.0–9.0
- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url
{
    NSLog(@"URL scheme:%@", [url scheme]);
    NSLog(@"URL query: %@", [url query]);
}


/// iOS 4.2–9.0
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{
    //通过sourceApplication来判断来自哪个app以决定要不要唤醒自己的app
    if ([sourceApplication isEqualToString:@"Bundle identifier"])
    {
        NSLog(@"%@", sourceApplication);    //来源于哪个app(Bundle identifier)
        NSLog(@"scheme:%@", [url scheme]);  //url scheme
        NSLog(@"query: %@", [url query]);   //可以通过[url query]来获得查询串  (传递参数)
        return YES;
    }
    else{
        return NO;
    }
}

APP1_唤起方需要做的事情:

1.唤起方法:

唤起方直接在点击事件里调用唤起方法:

/// 三个方法:

/// iOS 3.0+
- (BOOL)canOpenURL:(NSURL *)url;

/// iOS 10.0+
- (void)openURL:(NSURL *)url options:(NSDictionary<NSString *,id> *)options completionHandler:(void (^)(BOOL success))completion;

/// iOS 2.0–10.0
- (BOOL)openURL:(NSURL *)url;



//e.g. 唤起第三方App
- (void)evokeOtherApp {
    //app2是应用的唯一的scheme
    //scheme 必须是“app2://lanch” “weixin://app”类似的格式:
    NSURL *url = [NSURL URLWithString:@"app2://lanch?key=param"];

    if ([[UIApplication sharedApplication] canOpenURL:url]) {


        if ([[UIApplication sharedApplication] respondsToSelector:@selector(openURL:options:completionHandler:)]) {
            //iOS 10.0+
            [[UIApplication sharedApplication] openURL:url options:@{} completionHandler:^(BOOL success) {
            }];
        }else{
            //iOS 2~10
            [[UIApplication sharedApplication] openURL:url];
        }
    }
    else
    {
        //一般是没有安装
        NSLog(@"跳转下载app链接");
    }
}

2.sheme设置

如果你是iOS 9.0以上的系统,有时候会报错:

2017-01-05 11:06:24.343 APP1[235:48284] -canOpenURL: failed for URL: "app2://lanch?key=param" - error: "This app is not allowed to query for scheme app2"

因为:iOS 9.0以上的系统需要在“Info.plist”中将要使用的URL Schemes列为白名单,才可正常检查唤起的第三方应用是否安装。受此影响,当你的应用在iOS 9中需要使用 QQ/QQ空间/支付宝/微信SDK 的相关能力时,需要在“Info.plist”里增加白名单:

key>LSApplicationQueriesSchemes</key>
 <array>
    <!-- 微信 URL Scheme 白名单-->
    <string>wechat</string>
    <string>weixin</string>

    <!-- 新浪微博 URL Scheme 白名单-->
    <string>sinaweibohd</string>
    <string>sinaweibo</string>
    <string>sinaweibosso</string>
    <string>weibosdk</string>
    <string>weibosdk2.5</string>

    <!-- QQ、Qzone URL Scheme 白名单-->
    <string>mqqapi</string>
    <string>mqq</string>
    <string>mqqOpensdkSSoLogin</string>
    <string>mqqconnect</string>
    <string>mqqopensdkdataline</string>
    <string>mqqopensdkgrouptribeshare</string>
    <string>mqqopensdkfriend</string>
    <string>mqqopensdkapi</string>
    <string>mqqopensdkapiV2</string>
    <string>mqqopensdkapiV3</string>
    <string>mqzoneopensdk</string>
    <string>wtloginmqq</string>
    <string>wtloginmqq2</string>
    <string>mqqwpa</string>
    <string>mqzone</string>
    <string>mqzonev2</string>
    <string>mqzoneshare</string>
    <string>wtloginqzone</string>
    <string>mqzonewx</string>
    <string>mqzoneopensdkapiV2</string>
    <string>mqzoneopensdkapi19</string>
    <string>mqzoneopensdkapi</string>
    <string>mqzoneopensdk</string>

    <!-- 支付宝  URL Scheme 白名单-->
    <string>alipay</string>
    <string>alipayshare</string>

</array>

解决方案:

(1)在Info.plist里,配置支持http协议:

    <key>NSAppTransportSecurity</key>
    <dict>
        <key>NSAllowsArbitraryLoads</key>
        <true/>
    </dict>
`

(2)在Info.plist里,配置scheme到LSApplicationQueriesSchemes,也就是白名单:

<key>LSApplicationQueriesSchemes</key>
    <array>
        <string>app2</string>
        <string>app3</string>
        <string>app4</string>
    </array>

(3)延伸...?

// 代码动态把key加入白名单(不可行)
            var info = Bundle.main.infoDictionary
            guard var array:[Any] = info?["LSApplicationQueriesSchemes"] as? [Any] else {return}
            array.append(iosKey)
            print(array)
            var info2 = Bundle.main.infoDictionary
            print(info2)
1311718-efa511607a0751c3.png
跳转app_sheme设置.png

3.没有安装的处理

控制台输出错误:

2017-01-05 11:22:30.741 APP1[252:51626] -canOpenURL: failed for URL: "app3://lanch?key=param" - error: "(null)"
2017-01-05 11:22:30.746 APP1[252:51626] -canOpenURL: failed for URL: "app3://lanch?key=param" - error: "(null)"
2017-01-05 11:22:30.746 APP1[252:51626] [[UIApplication sharedApplication] canOpenURL:url] = 0

一般是没有安装App,可以跳转下载链接。

Demo下载地址:
链接: https://pan.baidu.com/s/1kViBtS7 密码: kvid

2019-07-10 11:59:15 qq_39714355 阅读数 14

什么是URL Scheme

简单的说,由于苹果选择使用沙盒机制来保障用户的隐私和安全,APP只能访问自己沙盒数据,但同时也阻碍了应用间合理的信息共享。因此苹果提供了一个可以在APP之间跳转的方法:URL Scheme。如果你的APP需要其他APP访问某些功能或者数据,那么你需要在你的APP定义一个相应的URL Scheme。当别的APP使用URL Scheme进行访问时,系统会根据URL Scheme进行匹配,从而来拉起对应的APP。

如何理解URL Scheme

如果想要更清晰的认识URL Scheme电话,我们需要了解下面几个概念:

  1. URL(Uniform Resource Locator:统一资源定位器):也就是我们所属于的"网址",通过它我们可以访问到我们想要的服务和资源,并且URL也可以传递相应的参数,也就是我们常说的GET请求;
  2. URL地址格式排列为:scheme://host:port/path,举个栗子:https://www.jianshu.com/u/b09c13696e1c就是个典型的URL,而这个网址对应的Scheme就是https,标识的是一个URL中的一个位置——最初始的位置,也可以理解为一种协议头。而我们自定义的URL Scheme可以理解为一种自定义的协议。
  3. 根据我们上面对URL Scheme的理解,我们可以很轻易地理解,在以本地应用为主的iOS上,我们可以像定位一个网页一样,用一种特殊的URL来定位一个应用甚至应用里某个具体的功能。而定位这个应用的标识,也就是Scheme。比如微信的Schemeweixin,打开微信扫一扫功能的URL Scheme则是weixin://dl/scan

这样一对比就容易很明白的理解出了URL Scheme的真正含义,它是为了在iOS系统中定位对应的App然后执行对应的操作,复杂的URL Scheme还可以传递参数。

URL Scheme的应用场景

  1. 使用iOS系统预设的URL Scheme调用系统APP:iOS系统内置的App,如mail,电话等等,都有相应的URL Scheme供其他的APP调用。比如下面的代码就是使用系统的电话APP给18888888888打电话。
    [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@“tel://18888888888”]];以下是一些URL Scheme合集:
  1. 使用URL Scheme让别的应用打开当前APP,自己的APP写好一下可以让其他APP来使用的功能,或者当前APP使用其他APP提供的服务。比如支付宝,当APP使用支付宝支付是,就可以使用支付宝定义好的Scheme来访问支付宝支付功能。因为URL可以携带一些参数因此我们也可以进行一些数据的共享。

  2. web页面通过URL Scheme来使用APP的一些功能。web可以通过window.location.href跳转方式跳转对应的URL Scheme从而来使用APP中一些功能。相对比较常用的URL Scheme的应用场景。

  3. 进行App内页面跳转。在传统意义上的页面跳转,无非也就是以下几种方式:

    • Storyboard的segues方式跳转
    • 直接跳转present,dismiss跳转
    • UINavigationController的push,pop跳转

    这些方式其实都有一个缺点,那就是跳转很不灵活,如果想让一个模块根据需求动态的跳转不同页面,传递不同的参数,那么就必须书写很多复杂的逻辑几句一些情况也选择要跳转的逻辑。
    或许你说我可以通过控制器的名字来创建对应的控制器进行动态的跳转,但事实上这样的方式如果仅仅进行跳转还是能满足需求的,但是在传递参数方面就是闲的力不从心。

所以说了那么多,有一种跳转方式可以既满足跳转的动态需求,也可以灵活的传递参数。这种方式就是使用URL Scheme进行动态跳转。这也是我非常推荐的一种使用方式。并且在一些组件化开发的尝试中,这种跳转方式也带来了很多便利。

使用URL Scheme跳转的好处

  1. URL Scheme跳转方式比较灵活,只需要本地进行简单逻辑处理,使用openURL来打开对应的控制器,而这个你想要打开的URL Scheme是可以动态的从服务器动态获取的。那么这样就很简单的实现了动态跳转。
  2. URL Scheme传递参数的方式也与URL一致,只需要简单的在URL里附加上对应的参数即可。
  3. 这种页面跳转是无差别的,通过URL Scheme跳转可以无缝的在H5页面和原生页面之间跳转传值,而无需做更多的逻辑判断。

使用URL Scheme跳转的缺点

  1. 写在info.plist文件中的Scheme可能会被一些反编译手段获取到。
  2. URL Scheme可能会被劫持调来安全隐患,比如支付宝的URL Scheme劫持漏洞。
    当然这是避免的,以下引自乌云:

苹果可以限制 iOS 应用不能注册别的应用的 Bundle ID 作为 URL Scheme。这样的话,使用自己的 Bundle ID 作为 URL Scheme 的接收器就会变的安全很多。

第三方应用可以通过①给自己发送 URL Scheme 请求来证明没有被劫持,如果没有收到自己的 URL Scheme,就可以及时给用户发送提醒;②利用 MobileCoreServices 服务中的 applicationsAvailableForHandlingURLScheme() 来查看所有注册了该 URL Schemes 的应用和处理顺序,从而检测自己、或者别人的 URL Scheme 是否被劫持。

注册自定义URL Scheme

注册自定义URL Scheme有两种方式

  1. 在工程中的info.plist文件中添加对应的key值
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  2. tagets -> info -> URL Types 中添加
    在这里插入图片描述
    在这里插入图片描述

这两种注册方式本质上其实是一样的只是位置不同。

URL Scheme相关验证

#从其他应用或Safari中使用URL Scheme拉起APP

我们需要在APPdelegate中实现相应的代理方法:

iOS 9.0以下
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
iOS 9.0以上
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation;

下面我们通过Safari来验证:
在这里插入图片描述
在这里插入图片描述

- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
    NSLog(@"%s",__func__);
    NSLog(@"options: %@", options);
    NSLog(@"URL scheme:%@", [url scheme]);
    NSLog(@"URL query: %@", [url query]);
    
    // 提示并展示query
    UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"打开URL Scheme成功"
                                                        message:[url query]
                                                       delegate:nil
                                              cancelButtonTitle:@"确定"
                                              otherButtonTitles:nil];
    [alertView show];
    
    return YES;
}

在这里插入图片描述

#在当前应用中使用URL Scheme

在应用中调用URL Scheme需要是以下方法:

iOS10.0以下使用该方法:
- (BOOL)openURL:(NSURL*)url NS_DEPRECATED_IOS(2_0, 10_0, "Please use openURL:options:completionHandler: instead") NS_EXTENSION_UNAVAILABLE_IOS("");
iOS10.0以上使用该方法:
- (void)openURL:(NSURL*)url options:(NSDictionary<UIApplicationOpenExternalURLOptionsKey, id> *)options completionHandler:(void (^ __nullable)(BOOL success))completion NS_AVAILABLE_IOS(10_0) NS_EXTENSION_UNAVAILABLE_IOS("");
还有一个方法可以判断对应URL Scheme是否存在一般和上述方法一起使用:
- (BOOL)canOpenURL:(NSURL *)url NS_AVAILABLE_IOS(3_0);

下面来验证一下:

- (void)btnClick:(UIButton *)sender {
    [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"schemeDemo://"] options:@{} completionHandler:^(BOOL success) {
        NSLog(@"完成");
    }];
}

在这里插入图片描述

文章若有不对地方,欢迎批评指正

没有更多推荐了,返回首页