Skip to content

Instantly share code, notes, and snippets.

@s-aska
Last active April 26, 2016 08:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save s-aska/9fe9c82b5d2ae4ff027ac1887e839fe8 to your computer and use it in GitHub Desktop.
Save s-aska/9fe9c82b5d2ae4ff027ac1887e839fe8 to your computer and use it in GitHub Desktop.

問題

SFSafariViewController内のShareボタンからShare Extensionを起動するとページタイトルが取得できない。

Safari

SFSafariViewController

検証

hipchatや自アプリからSFSafariViewControllerを起動しShareボタンをタップ、公式Twitterアプリ等でShareしようとするとURLだけがフィルされたシェアダイアログが出現する。

SafariアイコンからSafariで開き、SafariからShareを行うとページタイトルとURLがフィルされたシェアダイアログが出現する。

SafariのSheraの場合、contentTextに予めページタイトルがフィルされ、public.url によってURLを取得できる。

SFSafariViewControllerではcontentTextは空であり、public.data / public.item / public.url からURLを取得できるがページタイトルは取得できなかった。

解決策1

Shared NSUserDefaults によってページタイトルを引き渡す。

class SafariDelegate: NSObject, SFSafariViewControllerDelegate {

    // SFSafariViewController don't set page title to SLComposeServiceViewController's textView.text
    func safariViewController(controller: SFSafariViewController, activityItemsForURL URL: NSURL, title: String?) -> [UIActivity] {
        if let ud = NSUserDefaults.init(suiteName: "group.pw.aska.justaway") {
            ud.setURL(URL, forKey: "shareURL")
            ud.setObject(title ?? "", forKey: "shareTitle")
            ud.synchronize()
        }
        return []
    }
}

Shareタップ時に発火する activityItemsForURL によってページタイトルをセットし、Extension側でこれを受信する。

これはSFSafariViewControllerを開くアプリとExtensionが同じ作成元というケースでだけ有効なので、前述のhipchat等からもShareを受け付けたい場合は利用できない。

解決策2

Extension側でページをスクレイピングしてタイトルを取得する

let completion = { (data: NSData?, response: NSURLResponse?, error: NSError?) -> Void in
    guard let data = data, title = self.parseTitle(data) else {
        return
    }
    NSOperationQueue.mainQueue().addOperationWithBlock {
        self.textView.text = title + " " + self.textView.text
        self.textView.selectedRange = NSRange.init(location: 0, length: 0)
    }
}
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: configuration, delegate: nil, delegateQueue: nil)
session.dataTaskWithURL(pageURL, completionHandler: completion).resume()

問題点

httpサイトのページタイトルをスクレイピングするにはiOS9から導入されたATSを無効化する必要がある、つらい。

解決策3

ExtensionPreprocessingJS を使う

問題点

SFSafariViewControllerからのshareでは使いない!残念!

解決策4

FacebookのGraph APIを使う

let urlComponents = NSURLComponents(string: "https://graph.facebook.com/")!
urlComponents.queryItems = [
    NSURLQueryItem.init(name: "scrape", value: "true"),
    NSURLQueryItem.init(name: "id", value: pageURL.absoluteString),
]
guard let url = urlComponents.URL else {
    return
}
let req = NSMutableURLRequest.init(URL: url)
req.HTTPMethod = "POST"
let ogpCompletion = { (data: NSData?, response: NSURLResponse?, error: NSError?) -> Void in
    guard let data = data else {
        return
    }
    do {
        let json: AnyObject = try NSJSONSerialization.JSONObjectWithData(data, options: [])
        if let dic = json as? NSDictionary, title = dic["title"] as? String {
            NSOperationQueue.mainQueue().addOperationWithBlock {
                self.textView.text = title + " " + self.textView.text
                self.textView.selectedRange = NSRange.init(location: 0, length: 0)
            }
        }
    } catch _ as NSError {
    }
}
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: configuration, delegate: nil, delegateQueue: nil)
session.dataTaskWithRequest(req, completionHandler: ogpCompletion).resume()

問題点

トークン不要のAPIがあるが本来OGPキャッシュをクリアするためのものなので却下

解決策5

Amazon Lambda / API Gateway を使う

'use strict';
console.log('Loading function');

const http = require('http');
const https = require('https');
const regexs = [
    new RegExp('<meta property=["\']og:title["\'] content=["\']([^>]+)["\']', 'i'),
    new RegExp('<meta content=["\']([^>]+)["\'] property=["\']og:title["\']', 'i'),
    new RegExp('<title>([^<]*)</title>', 'i'),
];

exports.handler = (event, context, callback) => {
    console.log('Received event:', JSON.stringify(event, null, 2));

    const url = event.url;
    const module = url.indexOf('https://') !== -1 ? https : http;

    module.get(url, function(res) {

        var body = '';
        res.setEncoding('utf8');

        res.on('data', function(chunk) {
            body += chunk;
        });

        res.on('end', function() {
            for (var i = 0; i < regexs.length; i ++) {
                var regex = regexs[i];
                var match = regex.exec(body);
                if (match && match[1]) {
                    callback(null, {"url": url, "title": match[1]});
                    return
                }
            }
            callback(null, {"url": url, "title": ""});
        });

    }).on('error', function(e) {
        callback(null, {"url": url, "title": "", "error": e.message});
    });
};

Justaway

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment