GVKun编程网logo

在wkwebview中启用摄像头和麦克风访问(webview打开摄像头)

15

在本文中,我们将带你了解在wkwebview中启用摄像头和麦克风访问在这篇文章中,我们将为您详细介绍在wkwebview中启用摄像头和麦克风访问的方方面面,并解答webview打开摄像头常见的疑惑,同

在本文中,我们将带你了解在wkwebview中启用摄像头和麦克风访问在这篇文章中,我们将为您详细介绍在wkwebview中启用摄像头和麦克风访问的方方面面,并解答webview打开摄像头常见的疑惑,同时我们还将给您一些技巧,以帮助您实现更有效的android – 在WebView中启用后退按钮、BAWKWebView BAWKWebView 分类封装 WKWebView、getUserMedia API及HTML5 调用摄像头和麦克风、iOS UIWebView 和 WKWebView 的 cookie 获取,设置,删除

本文目录一览:

在wkwebview中启用摄像头和麦克风访问(webview打开摄像头)

在wkwebview中启用摄像头和麦克风访问(webview打开摄像头)

我有一个针对移动设备进行了优化的Web应用程序,可用于getUserMedia访问网络摄像头和麦克风资源。

WKWebView要将这个应用包装为A,因为我想提供本机应用体验。我知道,iOS不允许通过浏览器访问摄像机-
但有什么办法来获得权限的网络摄像头/麦克风与本机代码(沿着封装)和饲料这对Web应用程序-也许在某种程度上指向getUserMedia一个本地流源?

答案1

小编典典

是的,看看cordova-plugin-iosrtc和cordova-plugin-wkwebview-
engine。插件背后的想法如下:

1. 创建一个定义各种WebRTC类和函数的JavaScript文件(WebRTC.js),并将调用传递给WKWebView,例如:

(function() {  if (!window.navigator) window.navigator = {};  window.navigator.getUserMedia = function() {    webkit.messageHandlers.callbackHandler.postMessage(arguments);  }})();

2. 在WKWebView中,在文档开始处插入脚本:

let contentController = WKUserContentController();contentController.add(self, name: "callbackHandler")let script = try! String(contentsOf: Bundle.main.url(forResource: "WebRTC", withExtension: "js")!, encoding: String.Encoding.utf8)contentController.addUserScript(WKUserScript(source: script, injectionTime: WKUserScriptInjectionTime.atDocumentStart, forMainFrameOnly: true))let config = WKWebViewConfiguration()config.userContentController = contentControllerwebView = WKWebView(frame: CGRect.zero, configuration: config)

3. 收听从JavaScript发送的消息:

class ViewController: UIViewController, WKUIDelegate, WKNavigationDelegate, WKScriptMessageHandler {  var webView: WKWebView!  func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {    if message.name == "callbackHandler" {      print(message.body)      // make native calls to the WebRTC framework here    }  }}

4. 如果需要在JavaScript领域中执行成功或失败回调,请直接在WKWebView中评估函数调用:

webView.evaluateJavaScript("callback({id: \(id), status: ''success'', args: ...})", completionHandler: nil)

这些回调需要 调用 之前
存储在JavaScript中的哈希中postMessage,然后必须将哈希键发送到WKWebView。这是commandId插件中的。

int exec_id = 0;function exec(success, failure, ...) {  // store the callbacks for later  if (typeof success == ''function'' || typeof failure == ''function'') {    exec_id++;    exec_callbacks[exec_id] = { success: success, failure: failure };    var commandId = exec_id;  }  webkit.messageHandlers.callbackHandler.postMessage({id: commandId, args: ...})}// the native code calls this directly with the same commandId, so the callbacks can be performed and releasedfunction callback(opts) {  if (opts.status == "success") {    if (typeof exec_callbacks[opts.id].success == ''function'') exec_callbacks[opts.id].success(opts.args);  } else {    if (typeof exec_callbacks[opts.id].failure == ''function'') exec_callbacks[opts.id].failure(opts.args);  }  // some WebRTC functions invoke the callbacks multiple times  // the native Cordova plugin uses setKeepCallbackAs(true)  if (!opts.keepalive) delete exec_callbacks[opts.id];}

5.
当然,为您的项目添加NSCameraUsageDescriptionNSMicrophoneUsageDescription权限Info.plist

Keep in mind this is a non-trivial task, but that’s the general idea behind
bridging JavaScript, WKWebView, and native framework code with asynchronous
callbacks.

android – 在WebView中启用后退按钮

android – 在WebView中启用后退按钮

我刚刚开始使用 Android SDK.我想用WebView创建一个只显示本地网页的简单应用程序.我已经使用了我在网上找到的例子来制作它.

该应用程序工作得很好,直到我想添加一个后退按钮.当按下电话后退按钮时,应用程序崩溃.

这是我的MainActivity.java

package com.hspd.avhor;

import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.webkit.WebView;
import android.webkit.WebViewClient;

public class MainActivity extends Activity {

    WebView wv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        WebView wv = (WebView)findViewById(R.id.hspd_webview);
        wv.setWebViewClient(new WebViewClient() {
            @Override
            public boolean shouldOverrideUrlLoading(WebView view,String url){
                view.loadUrl(url);
                return true;
            }
        });
        wv.loadUrl("file:///android_asset/one.htm");
    }

    @Override
    public void onBackpressed()
    {
        if(wv.canGoBack())
            wv.goBack();
        else
            super.onBackpressed();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main,menu);
        return true;
    }

}

这是我的activity_main.xml:

<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fillViewport="true"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >

    <WebView
        android:id="@+id/hspd_webview"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</ScrollView>

这是我在logcat中的错误:

04-22 17:48:06.953: W/dalvikvm(799): threadid=1: thread exiting with uncaught exception (group=0x40a71930)
04-22 17:48:06.972: E/AndroidRuntime(799): FATAL EXCEPTION: main
04-22 17:48:06.972: E/AndroidRuntime(799): java.lang.NullPointerException
04-22 17:48:06.972: E/AndroidRuntime(799):  at com.hspd.avhor.MainActivity.onKeyDown(MainActivity.java:41)
04-22 17:48:06.972: E/AndroidRuntime(799):  at android.view.KeyEvent.dispatch(KeyEvent.java:2609)
04-22 17:48:06.972: E/AndroidRuntime(799):  at android.app.Activity.dispatchKeyEvent(Activity.java:2375)
04-22 17:48:06.972: E/AndroidRuntime(799):  at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:1847)
04-22 17:48:06.972: E/AndroidRuntime(799):  at android.view.ViewRootImpl.deliverKeyEventPostIme(ViewRootImpl.java:3701)
04-22 17:48:06.972: E/AndroidRuntime(799):  at android.view.ViewRootImpl.handleImeFinishedEvent(ViewRootImpl.java:3651)
04-22 17:48:06.972: E/AndroidRuntime(799):  at android.view.ViewRootImpl$ViewRootHandler.handleMessage(ViewRootImpl.java:2818)
04-22 17:48:06.972: E/AndroidRuntime(799):  at android.os.Handler.dispatchMessage(Handler.java:99)
04-22 17:48:06.972: E/AndroidRuntime(799):  at android.os.Looper.loop(Looper.java:137)
04-22 17:48:06.972: E/AndroidRuntime(799):  at android.app.ActivityThread.main(ActivityThread.java:5041)
04-22 17:48:06.972: E/AndroidRuntime(799):  at java.lang.reflect.Method.invokeNative(Native Method)
04-22 17:48:06.972: E/AndroidRuntime(799):  at java.lang.reflect.Method.invoke(Method.java:511)
04-22 17:48:06.972: E/AndroidRuntime(799):  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:793)
04-22 17:48:06.972: E/AndroidRuntime(799):  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:560)
04-22 17:48:06.972: E/AndroidRuntime(799):  at dalvik.system.NativeStart.main(Native Method)

提前致谢 :)

解决方法

覆盖onKeyDown(params),检查按下的键是否为后退按钮,检查webview是否可以导航到上一页,如果是,则导航到上一页.如果没有要显示的网页,您可以完成活动

您可以使用以下

@Override
public boolean onKeyDown(int keyCode,KeyEvent event) {
    if ((keyCode == KeyEvent.KEYCODE_BACK) && webView.canGoBack()) { 
            //if Back key pressed and webview can navigate to prevIoUs page
        webView.goBack();
            // go back to prevIoUs page
        return true;
    }
    else
    {
        finish();
           // finish the activity
    }
    return super.onKeyDown(keyCode,event);
}

编辑:

删除xml布局中的scrollview.试试下面的内容

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >

<WebView
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:id="@+id/wv"></WebView>
</LinearLayout>

编辑:2

这是一个有效的样本.在三星galaxy s3上测试过

activtiy_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >

<WebView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:id="@+id/wv"/>

</RelativeLayout>

主要活动

public class MainActivity extends Activity {
 private WebView webView;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);


        setContentView(R.layout.activity_main);
        webView = (WebView) findViewById(R.id.wv);
        webView.getSettings().setJavaScriptEnabled(true);
        webView.setWebChromeClient(new WebChromeClient() {
               public void onProgressChanged(WebView view,int progress) {
                MainActivity.this.setProgress(progress * 1000);
               }
             });
             webView.setWebViewClient(new WebViewClient() {
               public void onReceivedError(WebView view,int errorCode,String description,String failingUrl) {
                 Toast.makeText(MainActivity.this,"Oh no! " + description,Toast.LENGTH_SHORT).show();
               }
             });

             webView.loadUrl("http://slashdot.org/");

}   



@Override
public boolean onKeyDown(int keyCode,KeyEvent event) {
    if ((keyCode == KeyEvent.KEYCODE_BACK) && webView.canGoBack()) {
        webView.goBack();
        return true;
    }
    else
    {
        finish();
    }
    return super.onKeyDown(keyCode,event);
}
}

BAWKWebView BAWKWebView 分类封装 WKWebView

BAWKWebView BAWKWebView 分类封装 WKWebView

BAWKWebView BAWKWebView 介绍

BAWKWebView

  • 1、用分类封装 WKWebView,代码无任何侵入更改

  • 2、用 block 方式实现所需代理回调,更加方便,代码更简洁

  • 3、一行代码搞定 request、URL、URLString、本地 HTML文件、HTMLString等请求

  • 4、一个 block 搞定 title、progress、currentURL、当前网页的高度等等所需

  • 5、有博爱封装好的 BAWebViewController 可以直接使用,也可以参考自定义浏览器【参考demo】

  • 6、WKWebView 自定义 request post 数据到 JS(使用分类)

  • 7、WKWebView OC 拦截 JS URL 处理,详见demo

  • 8、修复 WKWebView 中的 alert 不能弹出的 bug!(详见 demo5)

  • 9、新增 cell 中 WKWebView 高度自适应 demo(demo 有小部分遗留问题待解决)

  • 10、自定义修改 navigator.userAgent(详见 demo BAWebViewController)


BAWKWebView BAWKWebView 官网

https://github.com/BAHome/BAWKWebView

getUserMedia API及HTML5 调用摄像头和麦克风

getUserMedia API及HTML5 调用摄像头和麦克风

getUserMedia API简介

HTML5的getUserMedia API为用户提供访问硬件设备媒体(摄像头、视频、音频、地理位置等)的接口,基于该接口,开发者可以在不依赖任何浏览器插件的条件下访问硬件媒体设备。 
getUserMedia API最初是navigator.getUserMedia,目前已被最新Web标准废除,变更为navigator.mediaDevices.getUserMedia(),但浏览器支持情况不如旧版API普及。 
MediaDevices.getUserMedia()方法提示用户允许使用一个视频和/或一个音频输入设备,例如相机或屏幕共享和/或麦克风。如果用户给予许可,就返回一个Promise对象,MediaStream对象作为此Promise对象的Resolved[成功]状态的回调函数参数,相应的,如果用户拒绝了许可,或者没有媒体可用的情况下PermissionDeniedError或者NotFoundError作为此PromiseRejected[失败]状态的回调函数参数。注意,由于用户不会被要求必须作出允许或者拒绝的选择,所以返回的Promise对象可能既不会触发resolve也不会触发 reject

浏览器兼容性

 

语法

navigator.mediaDevices.getUserMedia(constraints)
.then(function(mediaStream) { ... })
.catch(function(error) { ... })

 

参数

containers:指定请求的媒体类型,主要包含videoaudio,必须至少一个类型或者两个同时可以被指定。如果浏览器无法找到指定的媒体类型或者无法满足相对应的参数要求,那么返回的Promise对象就会处于rejected[失败]状态,NotFoundError作为rejected[失败]回调的参数。

【例】同时请求不带任何参数的音频和视频:

{ audio: true, video: true }

【例】使用1280x720的摄像头分辨率:

{
  audio: true,
  video: { width: 1280, height: 720 }
}

【例】要求获取最低为1280x720的分辨率:

{
  audio: true,
  video: {
    width: { min: 1024, ideal: 1280, max: 1920 },
    height: { min: 776, ideal: 720, max: 1080 }
  }
}

当请求包含一个ideal(应用最理想的)值时,这个值有着更高的权重,意味着浏览器会先尝试找到最接近指定的理想值的设定或者摄像头(如果设备拥有不止一个摄像头)。

【例】优先使用前置摄像头(如果有的话):

{ audio: true, video: { facingMode: "user" } }

【例】强制使用后置摄像头:

{ audio: true, video: { facingMode: { exact: "environment" } } }

 


成功回调函数seccessCallback的参数streamstreamMediaStream的对象,表示媒体内容的数据流,可以通过URL.createObjectURL转换后设置为VideoAudio元素的src属性来使用,部分较新的浏览器也可以直接设置为srcObject属性来使用。

 

注意:新版的谷歌浏览器不能直接将MediaStream对象直接作为URL.createObjectURL的参数使用,会报TypeError Failed to execute ''createObjectURL'' on ''URL'': No function was found that matched the signature provided的错误,具体用法在后面说明。


失败回调函数errorCallback的参数error,可能的异常有:

  • AbortError:硬件问题
  • NotAllowedError:用户拒绝了当前的浏览器实例的访问请求;或者用户拒绝了当前会话的访问;或者用户在全局范围内拒绝了所有媒体访问请求。
  • NotFoundError:找不到满足请求参数的媒体类型。
  • NotReadableError:操作系统上某个硬件、浏览器或者网页层面发生的错误导致设备无法被访问。
  • OverConstrainedError:指定的要求无法被设备满足。
  • SecurityError:安全错误,在getUserMedia() 被调用的 Document
    上面,使用设备媒体被禁止。这个机制是否开启或者关闭取决于单个用户的偏好设置。
  • TypeError:类型错误,constraints对象未设置[空],或者都被设置为false

示例:HTML 5调用媒体设备摄像头

这个例子中,请求访问用户硬件设备的摄像头,并把视频流通过Video元素显示出来。网页中提供一个"拍照"的按钮,通过Canvas将Video的画面截取并绘制,核心代码如下:

HTML

<!--video用于显示媒体设备的视频流,自动播放-->
<video id="video" autoplay style="width: 480px;height: 320px"></video>
<!--拍照按钮-->
<div>
<button id="capture">拍照</button>
</div>
<!--描绘video截图-->
<canvas id="canvas" width="480" height="320"></canvas>

 

JavaScript

//访问用户媒体设备的兼容方法
function getUserMedia(constrains,success,error){
    if(navigator.mediaDevices.getUserMedia){
        //最新标准API
        navigator.mediaDevices.getUserMedia(constrains).then(success).catch(error);
    } else if (navigator.webkitGetUserMedia){
        //webkit内核浏览器
        navigator.webkitGetUserMedia(constrains).then(success).catch(error);
    } else if (navigator.mozGetUserMedia){
        //Firefox浏览器
        navagator.mozGetUserMedia(constrains).then(success).catch(error);
    } else if (navigator.getUserMedia){
        //旧版API
        navigator.getUserMedia(constrains).then(success).catch(error);
    }
}

var video = document.getElementById("video");
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");

//成功的回调函数
function success(stream){
    //兼容webkit内核浏览器
    var CompatibleURL = window.URL || window.webkitURL;
    //将视频流设置为video元素的源
    video.src = CompatibleURL.createObjectURL(stream);   // 此处的代码将会报错  解决的办法是将video的srcObject属性指向stream即可
    //播放视频
    video.play();
}

//异常的回调函数
function error(error){
    console.log("访问用户媒体设备失败:",error.name,error.message);
}
if (navigator.mediaDevices.getUserMedia || navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia){
    //调用用户媒体设备,访问摄像头
    getUserMedia({
        video:{width:480,height:320}
    },success,error);
} else {
    alert("你的浏览器不支持访问用户媒体设备");
}

//注册拍照按钮的单击事件
document.getElementById("capture").addEventListener("click",function(){
    //绘制画面
    context.drawImage(video,0,0,480,320);
});

关闭摄像头或者麦克风:需要注意的是,MediaStream.getTracks() 返回的Tracks数组是按第一个参数倒序排列的

比如现在定义了

{
    video: true,
    audio: true
}

想关闭摄像头,就需要调用MediaStream.getTracks()[1].stop();

同理,0对应于audio的track

进阶

对本示例进行功能加强,比如使用CSS 3 的滤镜实现模糊、黑白等效果。

 

麦克风

因为纯粹用一个audio标签来播放麦克风拾取到的声音显得太没特色了,于是我用到了以前写的一个音频可视化库Vudio.js,代码如下:

创建一个canvas来显示音频波形图

<canvas id="canvas"></canvas>

通过Vudio.js和getUserMedia来显示麦克风拾取到的音频的波形

var canvas = document.querySelector(''#canvas'')

navigator.mediaDevices.getUserMedia({
 audio: true
}).then((stream) => {

  // 调用Vudio
  var vudio = new Vudio(stream, canvas, {
    accuracy: 256,
    width: 1024,
    height: 200,
    waveform: {
      fadeSide: false,
      maxHeight: 200,
      verticalAlign: ''middle'',
      horizontalAlign: ''center'',
      color: ''#2980b9''
    }
  })

  vudio.dance()

}).catch((error) => {
 console.error(error.name || error)
})

 vudio.js源码:https://github.com/margox/vudio.js/blob/master/vudio.js

效果如下图所示:

 

 

 

iOS UIWebView 和 WKWebView 的 cookie 获取,设置,删除

Cookie简介 说到Cookie,或许有些小伙伴会比较陌生,有些小伙伴会比较熟悉。如果项目中,所有页面都是纯原生来实现的话,一般Cookie这个东西或许我们永远也不会接触到。但是,这里还是要说一下Cookie,因为它真的很重要,由它产生的一些坑也很多。

Cookie 在 web中应用比较多,主要是记录一个状态,比如我在网页上登录了,我就可以拿到网页登录后 Cookie,下次再 Cookie 的生效期内我就可以不用输入账号密码,直接跳转登录状态,在App中,Cookie最常用的也就是维持登录状态了.因为笔者最近就在做这个,其中也遇到过很多坑,这里说先踩坑和用法

iOS Cookie 的管理

NSHTTPCookie和NSHTTPCookieStorage iOS中进行HTTP网络请求Cookie管理主要由两个类负责,一个类是NSHTTPCookieStorage类,一个是NSHTTPCookie类。

NSHTTPCookieStorage

NSHTTPCookieStorage类采用单例的设计模式,其中管理着所有HTTP请求的Cookie信息 官方解释:NSHTTPCookieStorage 是一个用来管理 cookie 存储的单例。一个 NSHTTPCookie 单例代表一个 cookie。通常来讲,cookie 可以在应用间共享,并且在进程之间保持同步。 对于单进程,Session cookies (这里的 cookie 对象的 isSessionOnly 方法返回 YES)是局部的并且不能被共享。

常用方法

// 获取单例对象
+ (NSHTTPCookieStorage *)sharedHTTPCookieStorage;

// 所有Cookie数据数组 其中存放NSHTTPCookie对象
@property (nullable , readonly, copy) NSArray<NSHTTPCookie *> *cookies;

// 手动设置一条Cookie数据
- (void)setCookie:(NSHTTPCookie *)cookie;

// 删除某条Cookie信息
- (void)deleteCookie:(NSHTTPCookie *)cookie;

// 删除某个时间后的所有Cookie信息 iOS8后可用
- (void)removeCookiesSinceDate:(NSDate *)date NS_AVAILABLE(10_10, 8_0);

// 获取某个特定URL的所有Cookie数据
- (nullable NSArray<NSHTTPCookie *> *)cookiesForURL:(NSURL *)URL;

// 为某个特定的URL设置Cookie
- (void)setCookies:(NSArray<NSHTTPCookie *> *)cookies forURL:(nullable NSURL *)URL mainDocumentURL:(nullable NSURL *)mainDocumentURL;

// Cookie数据的接收协议
枚举如下:
typedef NS_ENUM(NSUInteger, NSHTTPCookieAcceptPolicy) {
    NSHTTPCookieAcceptPolicyAlways,                     //接收所有Cookie信息
    NSHTTPCookieAcceptPolicyNever,                      //不接收所有Cookie信息
    NSHTTPCookieAcceptPolicyOnlyFromMainDocumentDomain  //只接收主文档域的Cookie信息
};
@property NSHTTPCookieAcceptPolicy cookieAcceptPolicy;

系统下面的两个通知与Cookie管理有关:
据说,在Mac OS是cookie可以共享的(Session cookies 不能共享),在Mac OS app中更改cookie的接收策略会影响到其他正在运行的在使用cookie storage的app.这时NSHTTPCookieStorage会发出两个通知:

// Cookie数据的接收协议改变时发送的通知
FOUNDATION_EXPORT NSString * const NSHTTPCookieManagerAcceptPolicyChangedNotification;
// 管理的Cookie数据发生变化时发送的通知
FOUNDATION_EXPORT NSString * const NSHTTPCookieManagerCookiesChangedNotification;

NSHTTPCookie介绍

NSHTTPCookie是具体的HTTP请求Cookie数据对象.

// 下面两个方法用于对象的创建和初始化 都是通过字典进行键值设置
- (nullable instancetype)initWithProperties:(NSDictionary<NSString *, id> *)properties;
+ (nullable NSHTTPCookie *)cookieWithProperties:(NSDictionary<NSString *, id> *)properties;

// 返回Cookie数据中可用于添加HTTP头字段的字典
+ (NSDictionary<NSString *, NSString *> *)requestHeaderFieldsWithCookies:(NSArray<NSHTTPCookie *> *)cookies;

// 从指定的响应头和URL地址中解析出Cookie数据
+ (NSArray<NSHTTPCookie *> *)cookiesWithResponseHeaderFields:(NSDictionary<NSString *, NSString *> *)headerFields forURL:(NSURL *)URL;

// Cookie数据中的属性字典
@property (nullable, readonly, copy) NSDictionary<NSString *, id> *properties;

// 请求响应的版本
@property (readonly) NSUInteger version;

// 请求相应的名称
@property (readonly, copy) NSString *name;

// 请求相应的值
@property (readonly, copy) NSString *value;

// 过期时间
@property (nullable, readonly, copy) NSDate *expiresDate;

// 请求的域名
@property (readonly, copy) NSString *domain;

//请求的路径
@property (readonly, copy) NSString *path;

// 是否是安全传输
@property (readonly, getter=isSecure) BOOL secure;

// 是否只发送HTTP的服务
@property (readonly, getter=isHTTPOnly) BOOL HTTPOnly;

// 响应的文档
@property (nullable, readonly, copy) NSString *comment;

// 相应的文档URL
@property (nullable, readonly, copy) NSURL *commentURL;

// 服务端口列表
@property (nullable, readonly, copy) NSArray<NSNumber *> *portList;

HTTP cookie的属性键 属性 | 解读

NSHTTPCookieName        |	Cookie的名字
NSHTTPCookieValue       |	Cookie的值
NSHTTPCookieOriginURL   |	和域名一样,NSHTTPCookieDomain或NSHTTPCookieOriginURL必须指定一个值
NSHTTPCookieVersion     |	接收器的版本
NSHTTPCookieDomain      |	域名
NSHTTPCookiePath        |	Cookie 存放路径
NSHTTPCookieSecure      |   Cookie是否只应通过安全通道发送,设置Cookie的secure属性为true。
                            只会在HTTPS和SSL等安全协议中传输此类Cookie。默认为false
NSHTTPCookieComment     |	包含Cookie的评论,只有有效的版本1的cookies或更高版本。 这头字段是可选的
NSHTTPCookieCommentURL  |	接收器的评论URL
NSHTTPCookieDiscard     |   Cookie是否应在会议结束时丢弃NSString,字符串值必须是“true”或“假”。 
                            这个字段是可选的。 默认为“假”,除非这是Cookie是第1版或以上,
                            NSHTTPCookieMaximumAge未指定,在这种情况下,它被假定为“TRUE”
NSHTTPCookieMaximumAge  |	NSString对象,包含一个整数,在Cookie内保持最多几秒 。
                            仅适用于第1版和更高版本的有效。 默认为“0”。 此字段是可选的
NSHTTPCookiePort        |	接收机的端口
  • UIWebView的 Cookie 机制

UIWebView 在浏览网页后会将网页中的 cookie 自动存入 NSHTTPCookieStorage 标准容器中,[NSHTTPCookieStorage sharedHTTPCookieStorage]这个单例管理,在后续访问中会将 cookie 自动带到 request 请求当中。并且在同一个app内多个UIWebView之间共享。

  • webView 中获取 cookie
//加载成功
- (void)webViewDidFinishLoad:(UIWebView *)webView {
    NSString *requestUrl = webView.request.URL.absoluteString;
    NSLog(@" requestUrl: %@",requestUrl);
    
    //设置原始 cookie 根据key 存储本地
    NSMutableArray *cookieArray = [[NSMutableArray alloc] init];
    
    //网页加载完成取出 cookies
    NSArray *nCookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies];
    NSHTTPCookie *cookie;
    for (id c in nCookies) {
        if ([c isKindOfClass:[NSHTTPCookie class]]) {
            cookie=(NSHTTPCookie *)c;
            //我这里是cookie存入字典中 去重
            if ([cookie value]) {
                //如果 vaule 值不为 nil 存入字典中,
                [self.mutableDic setValue:[cookie value] forKey:[cookie name]];
            }
            //设置原始 cookie
            NSMutableDictionary *cookieProperties = [NSMutableDictionary dictionary];
            [cookieProperties setObject:cookie.name forKey:NSHTTPCookieName];
            [cookieProperties setObject:cookie.value forKey:NSHTTPCookieValue];
            [cookieProperties setObject:cookie.domain forKey:NSHTTPCookieDomain];
            [cookieProperties setObject:cookie.path forKey:NSHTTPCookiePath];
            [cookieProperties setObject:[[NSDate date] dateByAddingTimeInterval:2629743] forKey:NSHTTPCookieExpires];
            [cookieArray addObject:cookieProperties];
            
            [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:cookie];
    }
}

//cookie 存入本地
[[NSUserDefaults standardUserDefaults] setObject:cookieArray forKey:@"cookieArray"];
[[NSUserDefaults standardUserDefaults] synchronize];

//下面 cookie 去重是为了得到 key=Value;形式的字符串,这里由于我有需求这样做,实际中下面可以忽略
NSMutableString *cookieValue = [NSMutableString stringWithFormat:@""];
// cookie重复,先放到字典进行去重,再进行拼接
for (NSString *key in self.mutableDic) {
    NSString *appendString = [NSString stringWithFormat:@"%@=%@;", key, [self.mutableDic valueForKey:key]];
    [cookieValue appendString:appendString];
}
NSLog(@"######################## %@ ####################",cookieValue);
  • 设置 cookie
NSMutableArray* cookieDictionary = [[NSUserDefaults standardUserDefaults] valueForKey:@"cookieArray"];

NSLog(@"cookie dictionary found is %@",cookieDictionary);

if (cookieDictionary) 
{
    for (NSInteger i = 0; i < cookieDictionary.count; i++) 
    {
        NSLog(@"cookie found is %@",[cookieDictionary objectAtIndex:i]);
        
        NSDictionary *cookieDic = [cookieDictionary objectAtIndex:i];
        NSHTTPCookie *cookie = [NSHTTPCookie cookieWithProperties:cookieDic];
        [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:cookie];
    }
}
//设置请求之前加载 cookie 确保 cookie 在请求头之前设置
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:self.URLString] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10];

//加载网页
[self.webView loadRequest:request];

  • 删除 cookie
// 清空 cookie
- (void)deleteCookie {
    // 清空 cookie
    NSHTTPCookieStorage *cookieJar = [NSHTTPCookieStorage sharedHTTPCookieStorage];
    NSArray *_tmpArray = [NSArray arrayWithArray:[cookieJar cookies]];
    for (id obj in _tmpArray) {
        [cookieJar deleteCookie:obj];
    }
}
  • WKWebView 的 Cookie 机制

NSURLCache和NSHTTPCookieStroage无法操作(WKWebView)WebCore进程的缓存和Cookie WKWebView实例将会忽略任何的默认网络存储器(NSURLCache, NSHTTPCookieStorage, NSCredentialStorage) 和一些标准的自定义网络请求类(NSURLProtocol,等等.),WKWebView实例不会把Cookie存入到App标准的的Cookie容器(NSHTTPCookieStorage)中,因为 NSURLSession/NSURLConnection等网络请求使用NSHTTPCookieStorage进行访问Cookie,所以不能访问WKWebView的Cookie,现象就是WKWebView存了Cookie,其他的网络类如NSURLSession/NSURLConnection却看不到。这是很多人的说法。 还有一种是说法是通过实践发现 WKWebView 实例其实也会将 Cookie 存储于 NSHTTPCookieStorage 中,但存储时机有延迟,因为WKWebView内也有cookie的容器,而且每隔一段时间就和app侧NSHTTPCookieStorage进行同步,而且这个同步是进程级别的同步,而且这个同步是单向。 至于以上两种说法,最终WKWebView Cookie 问题在于 WKWebView 发起的请求不会自动带上存储于 NSHTTPCookieStorage 容器中的 Cookie。

  • iOS11 iOS11 的 API 可以解决该问题,只要是存在 WKHTTPCookieStore 里的 cookie,WKWebView 每次请求都会携带,存在 NSHTTPCookieStorage 的cookie,并不会每次都携带。于是会发生首次 WKWebView 请求不携带 Cookie 的问题。

  • ios 11 WKWebView cookie 的注入

在执行 -[WKWebView loadReques:] 前将 NSHTTPCookieStorage 中的内容复制到 WKHTTPCookieStore 中,以此来达到 WKWebView Cookie 注入的目的。示例代码如下:

[self copyNSHTTPCookieStorageToWKHTTPCookieStoreWithCompletionHandler:^{
    NSURL *url = [NSURL URLWithString:@"https://www.v2ex.com"];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    [_webView loadRequest:request];
}];

- (void)copyNSHTTPCookieStorageToWKHTTPCookieStoreWithCompletionHandler:(nullable void (^)())theCompletionHandler; {

    NSArray *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies];
    WKHTTPCookieStore *cookieStroe = self.webView.configuration.websiteDataStore.httpCookieStore;
    if (cookies.count == 0) {
        !theCompletionHandler ?: theCompletionHandler();
        return;
    }
    for (NSHTTPCookie *cookie in cookies) 
    {
        [cookieStroe setCookie:cookie completionHandler:^{
        if ([[cookies lastObject] isEqual:cookie]) 
        {
            !theCompletionHandler ?: theCompletionHandler();
            return;
        }
    }];
    }
}

  • ios11 之前 注入 Cookie 就是从之前保存 cookie 的 NSHTTPCookieStorage 中取出相关 Cookie,然后在再次请求访问的时候在 request 中注入 Cookie。注入Cookie同样有多种方式。

  • 1.JS注入1

//取出 storage 中的cookie并将其拼接成正确的形式

NSArray<NSHTTPCookie *> *tmp = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]; 
NSMutableString *jscode_Cookie = [@"" mutableCopy];

[tmp enumerateObjectsUsingBlock:^(NSHTTPCookie * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) 
{
    NSLog(@"%@ = %@", obj.name, obj.value);
    [jscode_Cookie appendString:[NSString stringWithFormat:@"document.cookie = ''%@=%@'';", obj.name, obj.value]];
}];

WKUserContentController* userContentController = WKUserContentController.new;
WKUserScript * cookieScript = [[WKUserScript alloc] initWithSource: @"" injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];

[userContentController addUserScript:cookieScript];
WKWebViewConfiguration* webViewConfig = WKWebViewConfiguration.new;
webViewConfig.userContentController = userContentController;
WKWebView * webView = [[WKWebView alloc] initWithFrame:CGRectMake(/*set your values*/) configuration:webViewConfig];
  • JS注入2
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation 
{
    [webView evaluateJavaScript:@"document.cookie =''TeskCookieKey1=TeskCookieValue1'';" completionHandler:^(id result, NSError *error) {
        //...
    }];
}

  • NSMutableURLRequest 注入Cookie
NSURL *url = request.URL;
NSMutableString *cookies = [NSMutableString string];
NSMutableURLRequest *requestObj = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10];

NSArray *tmp = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies];
NSDictionary *dicCookies = [NSHTTPCookie requestHeaderFieldsWithCookies:tmp];
NSString *cookie = [self readCurrentCookie];
[requestObj setValue:cookie forHTTPHeaderField:@"Cookie"];
[_webView loadRequest:requestObj];


-(NSString *)readCurrentCookie
{
    NSMutableDictionary *cookieDic = [NSMutableDictionary dictionary];
    NSMutableString *cookieValue = [NSMutableString stringWithFormat:@""];
    NSHTTPCookieStorage *cookieJar = [NSHTTPCookieStorage sharedHTTPCookieStorage];
    for (NSHTTPCookie *cookie in [cookieJar cookies]) {
        [cookieDic setObject:cookie.value forKey:cookie.name];
    }

    // cookie重复,先放到字典进行去重,再进行拼接
    for (NSString *key in cookieDic) {
    NSString *appendString = [NSString stringWithFormat:@"%@=%@;", key, [cookieDic valueForKey:key]];
        [cookieValue appendString:appendString];
    }
    return cookieValue;
}

获取Cookie

由于 WKWebView 的 Cookie 存储容器 WKWebsiteDataStore 是私有存储,所以无法从这里获取到Cookie,目前的方法是(1)从网站返回的 response headerfields 中获取。(2)通过调用js的方法获取 cookie。

  • 1.从网站返回的 response headerfields 中获取,因为cookie都存在http respone的headerfields,找到能获得respone的WKWebView回调,打印
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler {

    NSHTTPURLResponse *response = (NSHTTPURLResponse *)navigationResponse.response;
    NSArray *cookies =[NSHTTPCookie cookiesWithResponseHeaderFields:[response allHeaderFields] forURL:response.URL];
    
    //读取wkwebview中的cookie 方法1
    for (NSHTTPCookie *cookie in cookies) {
        // [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:cookie];
        NSLog(@"wkwebview中的cookie:%@", cookie);
    }
    
    //读取wkwebview中的cookie 方法2 读取Set-Cookie字段
    NSString *cookieString = [[response allHeaderFields] valueForKey:@"Set-Cookie"];
    NSLog(@"wkwebview中的cookie:%@", cookieString);

    //看看存入到了NSHTTPCookieStorage了没有
    NSHTTPCookieStorage *cookieJar2 = [NSHTTPCookieStorage sharedHTTPCookieStorage];
    for (NSHTTPCookie *cookie in cookieJar2.cookies) {
       NSLog(@"NSHTTPCookieStorage中的cookie%@", cookie);
    }

    //下面是将 原始cookie本地 化
    NSMutableArray *cookieArray = [[NSMutableArray alloc] init];

    for (NSHTTPCookie *cookie in [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]) {
    
        NSMutableDictionary *cookieProperties = [NSMutableDictionary dictionary];
        [cookieProperties setObject:cookie.name forKey:NSHTTPCookieName];
        [cookieProperties setObject:cookie.value forKey:NSHTTPCookieValue];
        [cookieProperties setObject:cookie.domain forKey:NSHTTPCookieDomain];
        [cookieProperties setObject:cookie.path forKey:NSHTTPCookiePath];
        [cookieProperties setObject:[NSNumber numberWithInt:cookie.version] forKey:NSHTTPCookieVersion];
        [cookieProperties setObject:[[NSDate date] dateByAddingTimeInterval:2629743] forKey:NSHTTPCookieExpires];
        [cookieArray addObject:cookieProperties];
    }

    [[NSUserDefaults standardUserDefaults] setValue:cookieArray forKey:@"cookieArray"];
    [[NSUserDefaults standardUserDefaults] synchronize];

    decisionHandler(WKNavigationResponsePolicyAllow);
}

// 页面加载完成之后调用需要重新给WKWebView设置Cookie防止因为a标签跳转,导致下一次跳转的时候Cookie丢失。
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation{

    //取出cookie
    NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];

    //js函数
    NSString *JSFuncString =
    @"function setCookie(name,value,expires)\
    {\
        var oDate=new Date();\
        oDate.setDate(oDate.getDate()+expires);\
        document.cookie=name+''=''+value+'';expires=''+oDate+'';path=/''\
    }\
    function getCookie(name)\
    {\
        var arr = document.cookie.match(new RegExp(''(^| )''+name+''=({FNXX==XXFN}*)(;|$)''));\
        if(arr != null) return unescape(arr[2]); return null;\
    }\
    function delCookie(name)\
    {\
        var exp = new Date();\
        exp.setTime(exp.getTime() - 1);\
        var cval=getCookie(name);\
        if(cval!=null) document.cookie= name + ''=''+cval+'';expires=''+exp.toGMTString();\
    }";
    
    //拼凑js字符串
    NSMutableString *JSCookieString = JSFuncString.mutableCopy;
    for (NSHTTPCookie *cookie in cookieStorage.cookies) {
        NSString *excuteJSString = [NSString stringWithFormat:@"setCookie(''%@'', ''%@'', 1);", cookie.name, cookie.value];
        [JSCookieString appendString:excuteJSString];
    }
    //执行js
    [webView evaluateJavaScript:JSCookieString completionHandler:^(id obj, NSError * _Nullable error) {
        NSLog(@"%@",error);
    }];
}

  • 通过 JS 获取 cookie
- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation {

[webView evaluateJavaScript:[NSString stringWithFormat:@"document.cookie"] completionHandler:^(id _Nullable response, NSError * _Nullable error) {
    if (response != 0) {
        NSLog(@"\n\n\n\n\n\n document.cookie%@,%@",response,error);
    }
}];
}

##注意 document.cookie 的方法获取 cookie并不支持跨越获取,如果设置 httponly则获取不到 cookie 不论是(1)还是(2)方法,似乎都无法解决302请求的 Cookie 问题。举例来说,假设你要访问网站A,在A中点击登录,跳转页面到B地址,在B中完成登录之后302跳转回A网站。此时cookie是存在于B地址的 response 中的,在A地址的 response 中并没有 cookie 的字段。然而我们只能获取到A地址的 response ,无法截获到B地址的response。因此获取不到该类型网站的 cookie 。 由于我并没有遇到302这样的问题,所有看了下网上的资料,希望对遇到这个问题的小伙伴一下办法,网上给出的解决办法是:

  • 1.加载一个本地为空的html,域名指向你的第一次加载的url的域名。
//加载本地html
[self.webView loadHTMLString:@"" baseURL:[NSURL URLWithString:@"https:/a.com"]];
  • 2.通过以下方法,在第一次加载完成后,将需要设置的Cookies设置到WKWebView中,因为是加载的本地的html以下方法会立即执行。
// 页面加载完成之后调用
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation{

    if (isFirstLoaded) {
        NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
        //js函数
        NSString *JSFuncString =
        @"function setCookie(name,value,expires)\
        {\
            var oDate=new Date();\
            oDate.setDate(oDate.getDate()+expires);\
            document.cookie=name+''=''+value+'';expires=''+oDate+'';path=/''\
        }";
        
        //拼凑js字符串,按照自己的需求拼凑Cookie
        NSMutableString *JSCookieString = JSFuncString.mutableCopy;
        for (NSHTTPCookie *cookie in cookieStorage.cookies) {
            if (![cookie.name isEqualToString:@"__cust"]) {
                NSString *excuteJSString = [NSString stringWithFormat:@"setCookie(''%@'', ''%@'', 3);", cookie.name, cookie.value];
                [JSCookieString appendString:excuteJSString];
            }
        }
    
        //执行js
        [webView evaluateJavaScript:JSCookieString completionHandler:^(id obj, NSError * _Nullable error) 
        {
            //加载真正的第一次Request
            [self loadRealRequest];
        }];
    }
}

如果cookie 存储到本地获取本地的 cookie

//修改从 storage 中读取 cookie 的方法
-(NSString *)readCurrentCookie{

    NSMutableArray* cookieDictionary = [[NSUserDefaults standardUserDefaults] valueForKey:@"cookieArray"];
    NSLog(@"cookie dictionary found is %@",cookieDictionary);
    
    for (int i=0; i < cookieDictionary.count; i++) {
        NSLog(@"cookie found is %@",[cookieDictionary objectAtIndex:i]);
        NSMutableDictionary* cookieDictionary1 = [[NSUserDefaults standardUserDefaults] valueForKey:[cookieDictionary objectAtIndex:i]];
        NSHTTPCookie *cookie = [NSHTTPCookie cookieWithProperties:cookieDictionary1];
        [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:cookie];
    }
    
    NSMutableDictionary *cookieDic = [NSMutableDictionary dictionary];
    NSMutableString *cookieValue = [NSMutableString stringWithFormat:@""];
    NSHTTPCookieStorage *cookieJar = [NSHTTPCookieStorage sharedHTTPCookieStorage];

    for (NSHTTPCookie *cookie in [cookieJar cookies]) {
        [cookieDic setObject:cookie.value forKey:cookie.name];
    }
    
    // cookie重复,先放到字典进行去重,再进行拼接
    for (NSString *key in cookieDic) 
    {
        NSString *appendString = [NSString stringWithFormat:@"%@=%@;", key, [cookieDic valueForKey:key]];
        [cookieValue appendString:appendString];
    }
    return cookieValue;
}

清除 cookie

#pragma mark - 清空cookie
-(void)deleCookie {
    // NSHTTPCookieStorage *cookieJar = [NSHTTPCookieStorage sharedHTTPCookieStorage];
    // NSArray *_tmpArray = [NSArray arrayWithArray:[cookieJar cookies]];
    // for (id obj in _tmpArray) {
    //  [cookieJar deleteCookie:obj];
    // }
    if (@available(iOS 9.0, *)) {//iOS9及以上
        WKWebsiteDataStore *dateStore = [WKWebsiteDataStore defaultDataStore];
        [dateStore fetchDataRecordsOfTypes:[WKWebsiteDataStore allWebsiteDataTypes]
        completionHandler:^(NSArray<WKWebsiteDataRecord *> * __nonnull records) 
        {
            for (WKWebsiteDataRecord *record in records)
            {
                //取消备注,可以针对某域名做专门的清除,否则是全部清除
                //if ( [record.displayName containsString:@"baidu"])             
                //{
                    [[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:record.dataTypes
                    forDataRecords:@[record] completionHandler:^
                    {
                        NSLog(@"Cookies for %@ deleted successfully",record.displayName);
                    }];
                //}
            }
        }];
    }
    else { //iOS9以下
        NSString *libraryPath = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0];
        NSString *cookiesFolderPath = [libraryPath stringByAppendingString:@"/Cookies"];
        NSError *errors;
        [[NSFileManager defaultManager] removeItemAtPath:cookiesFolderPath error:&errors];
    }
}

##最后 UIWebView 和 WKWebView 所遇到问题 cookie 同步,获取,删除,设置这些问题,目前就这么多解决办法吧,我一开始尝试用 WKWebView 获取 cookie 但是最后遇到网页跨域问题以及低版本兼容问题我还是换回 UIWebView 了,如果有更好的解决办法可以在下面留言,谢谢!

来自:https://www.cnblogs.com/ningmengcao-ios/p/9578218.html

原文出处:https://www.cnblogs.com/benxiaokang/p/ios-uiwebview-he-wkwebview-de-cookie-huo-qu-she-zh.html

今天关于在wkwebview中启用摄像头和麦克风访问webview打开摄像头的分享就到这里,希望大家有所收获,若想了解更多关于android – 在WebView中启用后退按钮、BAWKWebView BAWKWebView 分类封装 WKWebView、getUserMedia API及HTML5 调用摄像头和麦克风、iOS UIWebView 和 WKWebView 的 cookie 获取,设置,删除等相关知识,可以在本站进行查询。

本文标签: