I am trying to use cookies in iOS WKWebView like this:
import UIKit
import WebKit
class ViewWrapper: UIViewController, WKNavigationDelegate{
#IBOutlet weak var viewerWebKit: WKWebView!
var loginToken: String?
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(string: "url")!
let newcookie = HTTPCookie(properties: [
.domain: "domain",
.path: "/",
.name: "cookie name",
.value: "cookie value",
.secure: "FALSE",
.expires: NSDate(timeIntervalSinceNow: 31556926)
])
var request = URLRequest(url: url)
request.httpShouldHandleCookies = true
viewerWebKit.configuration.websiteDataStore.httpCookieStore.setCookie(newcookie!, completionHandler: {
print("cookie setup done")
self.viewerWebKit.load(request)
})
let refresh = UIBarButtonItem(barButtonSystemItem: .refresh, target: webView, action: #selector(viewerWebKit.reload))
toolbarItems = [refresh]
navigationController?.isToolbarHidden = false
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func loadView() {
viewerWebKit = WKWebView()
viewerWebKit.navigationDelegate = self
view = viewerWebKit
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
title = webView.title
}
}
Domain, path, name and value are 100% correct. But when I am trying to get them and print then on my website, no cookies are set. Website printing of cookies works well, because I did an android app for this and it worked well there.
Do I need to do something more to accept or store the cookies?
Here's a self-contained example of setting a cookie which should be able to be read from a website loaded in the web view. The key points are that the domain must match the website's domain, the cookie expiry must be set to a future date, and this only worked for me with the secure flag set to false, not sure why.
Using the below example I was able to see the cookie in the browser's inspection window.
import UIKit
import WebKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let wv = WKWebView();
view = wv;
let cookie = HTTPCookie(properties: [
.domain: ".example.com",
.path: "",
.name: "name",
.value: "hello world",
.expires: Date(timeIntervalSince1970: 1639655995)
])!
wv.loadHTMLString("", baseURL: URL(string: "http://www.example.com")!);
wv.configuration.websiteDataStore.httpCookieStore.setCookie(cookie)
}
}
Set cookie this way, and pass secure flag "FALSE" and path with "/"
let newcookie = HTTPCookie(properties: [
.domain: "domain",
.path: "/",
.name: "cookie name",
.value: "cookie value",
.secure: "FALSE",
.expires: NSDate(timeIntervalSinceNow: 31556926)
])
Set cookie and wait for completion block to load your page.
self.configuration.websiteDataStore.httpCookieStore.setCookie(headerCookie, completionHandler: {
print("cookie setup done")
viewerWebKit.load(URLRequest(url: url))
})
After want to see cookie are updated or not just Add cookie change value observer like this way.
WKWebsiteDataStore.default().httpCookieStore.add(self)
func cookiesDidChange(in cookieStore: WKHTTPCookieStore) {
cookieStore.getAllCookies({ (cookies) in
cookies.forEach({ (cookie) in
print(cookie.name)
})
})
}
Now you can see your cookie in cookiesDidChange method.
Related
I'm implementing an iOS app which authenticates to my web service and receives a token. I have embedded a WKWebView to my app so that I can open one specific web page in which I have javascript that tries to read the token from local storage. How can I init the WKWebView so that my token is accessible from the local storage?
Edit: Here is a discussion telling a way to enable local storage: iOS WKWebView does not support local storage but it seems that adding an item to local storage from app code is not possible.
Try setting the token in header of request that would pe passed to webview, as:
if let url = URL(string: "https://yourdomain.com") {
var request = URLRequest(url: url)
request.addValue("auth token value", forHTTPHeaderField: "Authorization")
}
you can add to local storage: key is AccessToken and value is the token
webView.configuration.userContentController.addUserScript(script())
func script() -> WKUserScript {
let source = "localStorage.setItem('AccessToken', '\(getAccessToken())');"
return WKUserScript(source: source, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
}
I have created a viewcontroller which will create webview instance for you.
class ViewController: UIViewController, WKNavigationDelegate {
var webView: WKWebView!
//Kind of crazy stuff
override func loadView() {
webView = WKWebView()
webView.navigationDelegate = self
view = webView
}
override func viewDidLoad() {
super.viewDidLoad()
//General view with page, followed by link
let url = URL(string: "https://google.com")! //example
webView.load(URLRequest(url: url))
}
//I do not sure, is that ui?
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
}
}
I am trying to set cookies in my iOS like this:
let url = URL(string: "url")!
let jar = HTTPCookieStorage.shared
let cookieHeaderField = ["Set-Cookie": "key1=value1, key2=value2"]
let cookies = HTTPCookie.cookies(withResponseHeaderFields: cookieHeaderField, for: url)
jar.setCookies(cookies, for: url, mainDocumentURL: url)
let request = URLRequest(url: url)
viewerWebKit.load(request)
Then I am printing them like this:
viewerWebKit.configuration.websiteDataStore.httpCookieStore.getAllCookies( { (cookies) in
cookies.forEach({ (cookie) in
print(cookie.name)
})
})
All cookies are printed and they seem to be set normally. But when I use the Web inspector of my safari to see, if they are really set then nothing is there. No cookies are set. What is the problem? Do I need to accept them? Is safari blocking them? How can I set them normally, to be visible in web inspector?
I also tried this approach:
import UIKit
import WebKit
class ViewWrapper: UIViewController, WKNavigationDelegate {
var loginToken: String?
#IBOutlet weak var viewerWebKit: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
var urlRequest = URLRequest(url: URL(string: "url")!)
urlRequest.httpShouldHandleCookies = true
let newcookie = HTTPCookie(properties: [
.domain: "domain",
.path: "",
.name: "key",
.value: "value",
.secure: "FALSE",
.expires: NSDate(timeIntervalSinceNow: 31556926)
])
viewerWebKit.configuration.websiteDataStore.httpCookieStore.setCookie(newcookie!, completionHandler: {
self.viewerWebKit.load(urlRequest)
})
viewerWebKit.configuration.websiteDataStore.httpCookieStore.getAllCookies( { (cookies) in
cookies.forEach({ (cookie) in
print(cookie.name)
})
})
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func loadView() {
viewerWebKit = WKWebView()
viewerWebKit.navigationDelegate = self
view = viewerWebKit
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
title = webView.title
}
func cookiesDidChange(in cookieStore: WKHTTPCookieStore) {
cookieStore.getAllCookies({ (cookies) in
cookies.forEach({ (cookie) in
print(cookie.name)
})
})
}
}
but it didn't work too.
This is what I see in safari debug console:
Cookies are not set.
This is what I see in Xcode's console.
So here it seems to be set. But it is not in reality. Printing code prints cookies. But they are not all visible in safari console. How is that possible? Cookies csrftoken and sessionid are set by website, not by my app. And they are visible in both printing and debug console.
Set cookie through this
urlRequest.httpShouldHandleCookies = true
also after request create set
self.configuration.websiteDataStore.httpCookieStore.setCookie("your_http_cookie", completionHandler: {
// Do whatever you want. I suggest loading your webview after cookie is set.
})
Implement this observers WKHTTPCookieStoreObserver in your class.
WKWebsiteDataStore.default().httpCookieStore.add(self)
func cookiesDidChange(in cookieStore: WKHTTPCookieStore) {
// Must implement otherwise wkwebview cookie not sync properly
self.httpCookieStore.getAllCookies { (cookies) in
cookies.forEach({ (cookie) in
// print your cookie here
})
}
}
I am currently developing a iOS app which can download PDF(Have form on it) from server to iPad, then user can fill in the form on the iPad.
The problem is we need to support chinese on the field input.
Here is the field.
When I use "Quick" input method to type any character, it repeat 3 times.
When I type one more time, it repeat again.
Do anyone have issues with typing Chinese in textfield using PDFKit on iOS?
Update, Add code work
For the server-side, just a endpoint can download pdf, the pdf already added textfield on it.
On client-side, pdf is downloaded in viewDidLoad() and set into pdfView as following code.
override func viewDidLoad() {
super.viewDidLoad()
if let code = self.code {
let url = URL(string : API.pdf + "/" + code)
let loading = self.showLoading()
var request = URLRequest(url: url!)
request.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: request, completionHandler: { (responseData: Data?, response: URLResponse?, error: Error?) in
DispatchQueue.main.async {
self.pdfView.document = PDFDocument(data : responseData!)
self.pdfView.scaleFactor = 1
self.pdfView.backgroundColor = UIColor.lightGray
self.pdfView.autoScales = true
loading.dismiss(animated: true, completion: nil)
}
})
task.resume()
}
}
In story board, it is just a UIView and set custom class as PDFView
Updated 2
After that, i do a simple testing.
I create a simple testing controller and load pdf with just a simple textfield annotation. Result is same.
import UIKit
import PDFKit
class PDFTestViewController : ViewController {
#IBOutlet weak var pdfView: PDFView!
override func viewDidLoad() {
super.viewDidLoad()
if let url = Bundle.main.url(forResource: "4547315264964", withExtension: "pdf"),
let doc = PDFDocument(url: url){
self.pdfView.document = doc
self.pdfView.scaleFactor = 1
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
I am rendering local web content on a WKWebView using a local server of GCDWebServer, but I have a cross-origin request due to cookies sitting in the backend. how could I configure a proxy that will solve this problem.
NOTE: I have try'd to implement something on GitHub called CorsProxy but it's outdated and frankly doesn't solve my problem, creating a proxy.
I have come across answers addressing a similar problem, however I am running my webView on a GCDWebServer and I don't know how to create such a proxy on this particular local server?
Any help?
Here's my code:
class ViewController: UIViewController, WKUIDelegate, WKNavigationDelegate, WKScriptMessageHandler {
var wkWebView: WKWebView!
var webServer = GCDWebServer()
var contentController = WKUserContentController()
func initWebServer() {
let folderPath = Bundle.main.path(forResource: "www", ofType: nil)
webServer.addGETHandler(forBasePath: "/", directoryPath: folderPath!, indexFilename: "index.html", cacheAge: 0, allowRangeRequests: true)
webServer.start(withPort: 8080, bonjourName: "GCD Web Server")
}
public override func viewDidLoad() {
super.viewDidLoad()
initWebServer()
let userScript = WKUserScript(source: "helloMsg2(\"boooo hoo hoo hoooo\")", injectionTime: .atDocumentEnd, forMainFrameOnly: true)
contentController.addUserScript(userScript)
contentController.add(self, name: "callback")
let config = WKWebViewConfiguration()
config.userContentController = contentController
wkWebView = WKWebView(frame: view.bounds, configuration: config)
wkWebView.scrollView.bounces = false
wkWebView.uiDelegate = self
wkWebView.navigationDelegate = self
view.addSubview(wkWebView!)
wkWebView.load(URLRequest(url: webServer.serverURL!))
}
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
if message.name == "callback" {
print("message from javaScript is: \(message.body)")
} else {
print("message from javaScript is: \(message.body)")
}
}
}
You will need to roll your own implementation of -addGETHandlerForBasePath...to add the Access-Control-Allow-Origin: * header since this API doesn't allow customization of the headers.
See the source code in https://github.com/swisspol/GCDWebServer/blob/master/GCDWebServer/Core/GCDWebServer.m#L1015.
This may help support CORS.
Just add Access-Control-Allow-Origin: * into the response headers.
Just do:
GCDWebServerResponse * yourResponse = [GCDWebServerResponse new];
[yourResponse setValue:#"*" forAdditionalHeader:#"Access-Control-Allow-Origin"];
I am trying to implement OAuth2 in my iOS app through Square but it's saying there is an error with my redirect_uri when I sign in successfully through the browser that pops up.
I'm using the OAuthSwift pod. This is what I have so far to set up the URL scheme so that the redirect should open my iOS app:
Square dashboard config:
AppDelegate:
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
print("hollaaaaaaaaaaaaaaaaa") // i never see this printed
OAuthSwift.handle(url: url)
return true
}
}
Target:
Controller that opens the browser:
class OAuthViewController: UIViewController {
#IBAction func signInButtonTapped(_ sender: AnyObject) {
print("tapped");
let oauthswift = OAuth2Swift(
consumerKey: "my token",
consumerSecret: "my secret",
authorizeUrl: "https://connect.squareup.com/oauth2/authorize?client_id=my_id",
responseType: "token"
)
oauthswift.authorize(
withCallbackURL: URL(string: "com.edmund.ios/oauth-callback")!, // doesn't seem to do anything honestly... I think the Square dashboard setting has precedence over this.
scope: "MERCHANT_PROFILE_READ%20PAYMENTS_READ%20ITEMS_READ%20ORDERS_READ",
state: "",
success: { (credential, response, parameters) -> Void in
print(credential)
},
failure: { error in
print(error.localizedDescription)
}
)
}
}
Redirect to ios app is possible? Completly possible
Here I will guide you simple approach to achieve this.
The square oAuth implementation can achieve by 2 simple easy steps without using any third-party libraries.
Benefits of this approach
You always stay within the application (because we use the in-app browser)
No need to add URI schema in the application (because we never leave the app)
Step 1: Add a view controller and attach a WKWebview;
Step 2: Load auth request URL and listen for redirect URI;
You can dismiss the controller and proceed with the access token once the redirection happens.
Redirect URI
You have to set a redirect URI in the square dashboard;
(Example: "http://localhost/square-oauth-callback")
but you are free to set any valid URL.
We monitor this url within our app.
Implement the following code in your application
import Foundation
import UIKit
import WebKit
class SquareAuthenticationViewController: UIViewController {
// MARK: Connection Objects
#IBOutlet weak var webView: WKWebView!
// MARK: Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
configureView()
initiateAuthentication()
}
func configureView() {
webView.navigationDelegate = self
}
func initiateAuthentication() {
// Validation
guard let url = getPath() else {
return
}
// Prepare request
let request = URLRequest(url: url)
webView.load(request)
}
func getPath() -> URL? {
let clientId = "Your Suare Application Id"
let scope = ["MERCHANT_PROFILE_READ",
"CUSTOMERS_READ",
"CUSTOMERS_WRITE",
"EMPLOYEES_READ",
"EMPLOYEES_WRITE",
"ITEMS_READ",
"PAYMENTS_READ"].joined(separator: " ")
let queryClientId = URLQueryItem(name: "client_id" , value: clientId.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed))
let queryScope = URLQueryItem(name: "scope" , value: scope.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed))
var components = URLComponents()
components.scheme = "https"
components.host = "connect.squareup.com"
components.path = "/oauth2/authorize"
components.percentEncodedQueryItems = [queryClientId, queryScope]
return components.url
}
}
extension SquareAuthenticationViewController: WKNavigationDelegate {
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
// here we handle internally the callback url and call method that call handleOpenURL (not app scheme used)
if let url = navigationAction.request.url, url.host == "localhost" , url.path == "/square-oauth-callback" {
print(url)
print(url.valueOf("code"))
//decisionHandler(.cancel)
/* Dismiss your view controller as normal
And proceed with OAuth authorization code
The code you receive here is not the auth token; For auth token you have to make another api call with the code that you received here and you can procced further
*/
/*
Auth Process Flow: https://developer.squareup.com/docs/oauth-api/how-it-works#oauth-access-token-management
Obtain Auth Token: https://developer.squareup.com/reference/square/oauth-api/obtain-token
*/
}
decisionHandler(.allow)
}
}
extension URL {
func valueOf(_ queryParamaterName: String) -> String? {
guard let url = URLComponents(string: self.absoluteString) else { return nil }
return url.queryItems?.first(where: { $0.name == queryParamaterName })?.value
}
}
When you guide a user through the oauth flow for your app, you must specify a redirect_uri parameter that matches that value you have specified in the Square developer portal. Note that this redirect_uri must start with http or https and correspond to a webpage on your server.
If you redirect the square endpoint to your server, if your sure they are running on iOS you can use your URL Scheme to reopen your app and pass any parameters that you wish