Fizzix
Fizzix

Reputation: 24375

How to get Swift to interact with a Webview?

I'm building a basic iOS app with Xcode that mainly just contains a webview with my web app inside.

I was wondering if there was a decent way to save the users username to the devices storage when logging in so that it can be automatically entered when opening the app next time. Since the app is a webview, I don't believe there is a way to keep the user logged in (like other major apps do, such as Facebook), so I think that auto filling the username will be beneficial for them.

I found this question and answer that could possibly solve my problem, although it's in good ol' Objective C.

My current attempt, that does absolutely nothing:

let savedUsername = "testusername"

let loadUsernameJS = "document.getElementById(\"mainLoginUsername\").value = " + savedUsername + ";"

self.Webview.stringByEvaluatingJavaScriptFromString(loadUsernameJS)

Is this a possibility with Swift?

Upvotes: 1

Views: 4556

Answers (3)

Casey
Casey

Reputation: 6701

for storing the password you should use the keychain, specifically web credentials. if done right, this will allow your app to use any existing keychain entries entered via Safari and will also allow Safari to access the password if saved via your app.

Code for setting and retrieving provided below:

private let domain = "www.youdomain.com"

func saveWebCredentials(username: String, password: String, completion: Bool -> Void) {
    SecAddSharedWebCredential(domain, username, password) { error in
        guard error == nil else { print("error saving credentials: \(error)"); return completion(false) }
        completion(true)
    }
}

func getExistingWebCredentials(completion: ((String, String)?, error: String?) -> Void) {
    SecRequestSharedWebCredential(domain, nil) { credentials, error in
        // make sure we got the credentials array back
        guard let credentials = credentials else { return completion(nil, error: String(CFErrorCopyDescription(error))) }

        // make sure there is at least one credential
        let count = CFArrayGetCount(credentials)
        guard count > 0 else { return completion(nil, error: "no credentials stored") }

        // extract the username and password from the credentials dict
        let credentialDict = unsafeBitCast(CFArrayGetValueAtIndex(credentials, 0), CFDictionaryRef.self)
        let username = CFDictionaryGetValue(credentialDict, unsafeBitCast(kSecAttrAccount, UnsafePointer.self))
        let password = CFDictionaryGetValue(credentialDict, unsafeBitCast(kSecSharedPassword, UnsafePointer.self))

        // return via completion block
        completion((String(unsafeBitCast(username, CFStringRef.self)), String(unsafeBitCast(password, CFStringRef.self))), error: nil)
    }
}

which is used like this:

// save the credentials
saveWebCredentials("hello", password: "world", completion: { success in

    // retrieve the credentials
    getExistingWebCredentials { credentials, error in
        guard let credentials = credentials else { print("Error: \(error)"); return }
        print("got username: \(credentials.0) password: \(credentials.1)")
    }
})

UPDATE

Recommend switching to using a WKWebView so you can easily pull out the response headers. Here is boilerplate code:

import UIKit
import WebKit

class ViewController: UIViewController, WKNavigationDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()

        let webView = WKWebView(frame: self.view.bounds)
        webView.navigationDelegate = self
        self.view.addSubview(webView)

        webView.loadRequest(NSURLRequest(URL: NSURL(string: "https://www.google.com")!))
    }

    func webView(webView: WKWebView, decidePolicyForNavigationResponse navigationResponse: WKNavigationResponse, decisionHandler: (WKNavigationResponsePolicy) -> Void) {
        // make sure the response is a NSHTTPURLResponse
        guard let response = navigationResponse.response as? NSHTTPURLResponse else { return decisionHandler(.Allow) }

        // get the response headers
        let headers = response.allHeaderFields
        print("got headers: \(headers)")

        // allow the request to continue
        decisionHandler(.Allow);
    }
}

Upvotes: 5

Matija Kraljic
Matija Kraljic

Reputation: 136

You are not passing a string to JavaScript, you should encapsulate the variable in additional quotes

let loadUsernameJS = "document.getElementById(\"mainLoginUsername\").value = \"" + savedUsername + "\";"

or

let loadUsernameJS = "document.getElementById('mainLoginUsername').value = '" + savedUsername + "';"

Upvotes: 0

Adrien Cadet
Adrien Cadet

Reputation: 1341

You code is not working because you did not wrap savedUsername with quotes.

You should have this instead:

let loadUsernameJS = "document.getElementById(\"mainLoginUsername\").value = \"\(savedUsername)\";"

Also, this library might help you.

Upvotes: 1

Related Questions