Can't set headers on my WKWebView POST request

As the OP stated, I have also confirmed in Charles that the body is 0 bytes after webView.load(request).

There's a workaround for this WKWebView bug, we will initiate a POST request using URLSession convert the data returned by the server to String and instead of loading the url we will use loadHTMLString which will:

Set the webpage contents and base URL.

and the content is our converted string:

var request = URLRequest(url: URL(string: "http://www.yourWebsite")!)
request.httpMethod = "POST"
let params = "do=something&andAgain=something"
request.httpBody = params.data(using: .utf8)

let task = URLSession.shared.dataTask(with: request) { (data : Data?, response : URLResponse?, error : Error?) in
        if data != nil {
            if let returnString = String(data: data!, encoding: .utf8) {
                self.webView.loadHTMLString(returnString, baseURL: URL(string: "http://www.yourWebsite.com")!)
            }
        }
}
task.resume()

I had the same problem with WKWebView, that I decided to use instead of UIWebView to avoid the pickers crash in iOS 8. There are two ways that I can think of:

  1. Use NSURLConnection to make the request and then fill the WKWebView with it's response data. You can find an example here: https://stackoverflow.com/a/10077796/4116680 (You only need connection:didReceiveData: and connectionDidFinishLoading: from the delegate if you don't use a self signed SSL certificate)
  2. Use a JavaScript to make the POST request. Here is an example:

Create file eg. "POSTRequestJS.html":

<html>
    <head>
        <script>
            //POST request example:
            //post('URL', {key: 'value'});
            function post(path, params) {
                var method = "post";
                var form = document.createElement("form");
                form.setAttribute("method", method);
                form.setAttribute("action", path);

                for(var key in params) {
                    if(params.hasOwnProperty(key)) {
                        var hiddenField = document.createElement("input");
                        hiddenField.setAttribute("type", "hidden");
                        hiddenField.setAttribute("name", key);
                        hiddenField.setAttribute("value", params[key]);

                        form.appendChild(hiddenField);
                    }
                }

                document.body.appendChild(form);
                form.submit();
            }
        </script>
    </head>
    <body>
    </body>
</html>

And in your code after where you want to load your request:

NSString *path = [[NSBundle mainBundle] pathForResource:@"POSTRequestJS" ofType:@"html"];
NSString *html = [[NSString alloc] initWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
WKWebView.navigationDelegate = self;
[WKWebView loadHTMLString:html baseURL:[[NSBundle mainBundle] bundleURL]];

Add method:

- (void)makePostRequest
{
    NSString *postData = [NSString stringWithFormat: @"email=%@&password=%@", email, password];
    NSString *urlString = @"http://materik.me/endpoint";
    NSString *jscript = [NSString stringWithFormat:@"post('%@', {%@});", urlString, postData];

    DLog(@"Javascript: %@", jscript);

    [WKWebView evaluateJavaScript:jscript completionHandler:nil];

    didMakePostRequest = YES;
}

And last add the WKNavigationDelegate:

- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation
{
    if (!didMakePostRequest) {
        [self makePostRequest];
    }
}

This appears to be a bug.
https://bugs.webkit.org/show_bug.cgi?id=140188

Hopefully it will be addressed soon. In the meantime, reverting to UIWebView or implementing the workaround proposed by Spas Bilyarski in his answer seems to be the best options.