Skip to content
This repository has been archived by the owner on Feb 22, 2022. It is now read-only.

Commit

Permalink
Add support for og:image icons.
Browse files Browse the repository at this point in the history
  • Loading branch information
leonbreedt committed Nov 3, 2018
1 parent 576b460 commit 833fb33
Show file tree
Hide file tree
Showing 8 changed files with 137 additions and 43 deletions.
10 changes: 5 additions & 5 deletions Example/Example/Base.lproj/Main.storyboard
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13771" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13772"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14460.20"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
Expand All @@ -25,10 +25,10 @@
<nil key="highlightedColor"/>
</label>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="cSf-Nm-dlD">
<rect key="frame" x="171" y="362" width="32" height="32"/>
<rect key="frame" x="155.5" y="362" width="64" height="64"/>
<constraints>
<constraint firstAttribute="width" constant="32" id="JlN-aE-C65"/>
<constraint firstAttribute="height" constant="32" id="Ojd-ki-eD9"/>
<constraint firstAttribute="width" constant="64" id="JlN-aE-C65"/>
<constraint firstAttribute="height" constant="64" id="Ojd-ki-eD9"/>
</constraints>
</imageView>
</subviews>
Expand Down
11 changes: 8 additions & 3 deletions Example/Example/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,26 @@ import FavIcon
class ViewController: UIViewController {
@IBOutlet private weak var imageView: UIImageView!
@IBOutlet private weak var statusLabel: UILabel!

let url = "https://youtube.com"

override func viewDidLoad() {
super.viewDidLoad()

statusLabel.text = "Loading..."
do {
try FavIcon.downloadPreferred("https://apple.com") { result in
try FavIcon.downloadPreferred(url) { result in
if case let .success(image) = result {
self.statusLabel.text = "Loaded."
self.statusLabel.text = "Loaded (\(image.size.width)x\(image.size.height))"
self.imageView.image = image
} else if case let .failure(error) = result {
self.statusLabel.text = "Failed: \(error.localizedDescription)."
print("failed to download preferred favicon for \(self.url): \(error)")
}
}
} catch let error {
statusLabel.text = "Failed."
print("failed to download preferred favicon for apple.com: \(error)")
print("failed to download preferred favicon for \(self.url): \(error)")
}
}
}
Expand Down
17 changes: 11 additions & 6 deletions Sources/FavIcon/Detection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,17 @@ func detectHTMLHeadIcons(_ document: HTMLDocument, baseURL: URL) -> [Icon] {
}

for meta in document.query(xpath: "/html/head/meta") {
guard let name = meta.attributes["name"]?.lowercased() else { continue }
guard let content = meta.attributes["content"] else { continue }
guard let url = URL(string: content, relativeTo: baseURL) else { continue }
guard let size = microsoftSizeHints[name] else { continue }

icons.append(Icon(url: url, type: .microsoftPinnedSite, width: size.width, height: size.height))
if let name = meta.attributes["name"]?.lowercased() {
guard let content = meta.attributes["content"] else { continue }
guard let url = URL(string: content, relativeTo: baseURL) else { continue }
guard let size = microsoftSizeHints[name] else { continue }
icons.append(Icon(url: url, type: .microsoftPinnedSite, width: size.width, height: size.height))
} else if let property = meta.attributes["property"]?.lowercased() {
guard let content = meta.attributes["content"] else { continue }
guard property == "og:image" else { continue }
guard let imageURL = URL(string: content, relativeTo: baseURL) else { continue }
icons.append(Icon(url: imageURL, type: .openGraphImage))
}
}

return icons
Expand Down
64 changes: 54 additions & 10 deletions Sources/FavIcon/FavIcon.swift
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ public final class FavIcon {
}
}

completion(downloadResults)
completion(sortResults(downloadResults))
}
}
}
Expand Down Expand Up @@ -217,17 +217,19 @@ public final class FavIcon {
height: Int? = nil,
completion: @escaping (IconDownloadResult) -> Void) throws {
scan(url) { icons in
guard let icon = chooseIcon(icons, width: width, height: height) else {
let sortedIcons = sortIcons(icons, preferredWidth: width, preferredHeight: height)
if sortedIcons.count == 0 {
DispatchQueue.main.async {
completion(.failure(error: IconError.noIconsDetected))
}
return
}

download([icon]) { results in
download(sortedIcons) { results in
let downloadResult: IconDownloadResult
if results.count > 0 {
downloadResult = results[0]
let sortedResults = sortResults(results, preferredWidth: width, preferredHeight: height)
if sortedResults.count > 0 {
downloadResult = sortedResults[0]
} else {
downloadResult = .failure(error: IconError.noIconsDetected)
}
Expand Down Expand Up @@ -257,12 +259,13 @@ public final class FavIcon {
guard let url = URL(string: url) else { throw IconError.invalidBaseURL }
try downloadPreferred(url, width: width, height: height, completion: completion)
}

static func chooseIcon(_ icons: [Icon], width: Int? = nil, height: Int? = nil) -> Icon? {
guard icons.count > 0 else { return nil }
static func sortIcons(_ icons: [Icon], preferredWidth: Int? = nil, preferredHeight: Int? = nil) -> [Icon] {
guard icons.count > 0 else { return [] }

let iconsInPreferredOrder = icons.sorted { left, right in
if let preferredWidth = width, let preferredHeight = height,
// Fall back to comparing dimensions.
if let preferredWidth = preferredWidth, let preferredHeight = preferredHeight,
let widthLeft = left.width, let heightLeft = left.height,
let widthRight = right.width, let heightRight = right.height {
// Which is closest to preferred size?
Expand All @@ -289,7 +292,43 @@ public final class FavIcon {
return left.type.rawValue < right.type.rawValue
}

return iconsInPreferredOrder.first!
return iconsInPreferredOrder
}

static func sortResults(_ results: [IconDownloadResult], preferredWidth: Int? = nil, preferredHeight: Int? = nil) -> [IconDownloadResult] {
guard results.count > 0 else { return [] }

let resultsInPreferredOrder = results.sorted { left, right in
switch (left, right) {
case (.success(let leftImage), .success(let rightImage)):
if let preferredWidth = preferredWidth, let preferredHeight = preferredHeight {
let widthLeft = leftImage.size.width
let heightLeft = leftImage.size.height
let widthRight = rightImage.size.width
let heightRight = rightImage.size.height

// Which is closest to preferred size?
let deltaA = abs(widthLeft - CGFloat(preferredWidth)) * abs(heightLeft - CGFloat(preferredHeight))
let deltaB = abs(widthRight - CGFloat(preferredWidth)) * abs(heightRight - CGFloat(preferredHeight))

return deltaA < deltaB
} else {
// Fall back to largest image.
return leftImage.area > rightImage.area
}
case (.success, .failure):
// An image is better than none.
return true
case (.failure, .success):
// An image is better than none.
return false
default:
return true
}
}

return resultsInPreferredOrder

}
}

Expand All @@ -302,3 +341,8 @@ extension Icon {
}
}

extension ImageType {
var area: CGFloat {
return size.width * size.height
}
}
2 changes: 2 additions & 0 deletions Sources/FavIcon/IconType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ public enum IconType: UInt {
case microsoftPinnedSite
/// An icon defined in a Web Application Manifest JSON file, mainly Android/Chrome.
case webAppManifest
/// An icon defined by the og:image meta property.
case openGraphImage
}


9 changes: 7 additions & 2 deletions Tests/FavIconTests/DetectionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ class DetectionTests: XCTestCase {
let document = HTMLDocument(string: html)
let icons = detectHTMLHeadIcons(document, baseURL: URL(string: "https://localhost")!)

XCTAssertEqual(20, icons.count)
XCTAssertEqual(21, icons.count)

XCTAssertEqual("https://localhost/shortcut.ico", icons[0].url.absoluteString)
XCTAssertEqual(IconType.shortcut.rawValue, icons[0].type.rawValue)
Expand Down Expand Up @@ -197,6 +197,11 @@ class DetectionTests: XCTestCase {
XCTAssertEqual(IconType.microsoftPinnedSite.rawValue, icons[19].type.rawValue)
XCTAssertEqual(310, icons[19].width!)
XCTAssertEqual(310, icons[19].height!)

XCTAssertEqual("https://www.facebook.com/images/fb_icon_325x325.png", icons[20].url.absoluteString)
XCTAssertEqual(IconType.openGraphImage.rawValue, icons[20].type.rawValue)
XCTAssertNil(icons[20].width)
XCTAssertNil(icons[20].height)
}

func testIssue6_ContentTypeWithEmptyComponent() {
Expand All @@ -221,7 +226,7 @@ class DetectionTests: XCTestCase {
XCTAssertEqual(0, document.query(xpath: "/BrowserConfig").count)
XCTAssertEqual(0, document.query(xpath: "").count)
}

private func pathForTestBundleResource(fileName: String) -> String {
let testBundle = Bundle(for: FavIconTests.self)
return testBundle.path(forResource: fileName, ofType: "")!
Expand Down
66 changes: 49 additions & 17 deletions Tests/FavIconTests/FavIconTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,37 @@ class FavIconTests: XCTestCase {
wait(for: [completed], timeout: 15)

XCTAssertNotNil(actualIcons)
XCTAssertEqual(1, actualIcons.count)
XCTAssertEqual(2, actualIcons.count)
XCTAssertEqual(URL(string: "https://apple.com/favicon.ico")!, actualIcons[0].url)
}

func testIssue24_LowResIcons() {
let url = "https://www.facebook.com"
var actualResult: IconDownloadResult!

let completed = expectation(description: "download: \(url)")
do {
try FavIcon.downloadPreferred(url) { result in
actualResult = result
completed.fulfill()
}
} catch let error {
XCTFail("failed to download icons: \(error)")
}
wait(for: [completed], timeout: 15)

XCTAssertNotNil(actualResult)

switch actualResult! {
case .success(let image):
XCTAssertEqual(325.0, image.size.width)
XCTAssertEqual(325.0, image.size.height)
break
case .failure(let error):
XCTFail("unexpected error returned for download: \(error)")
break
}
}

func testDownloading() {
let url = "https://apple.com"
Expand All @@ -56,12 +84,12 @@ class FavIconTests: XCTestCase {
}
wait(for: [completed], timeout: 15)

XCTAssertEqual(1, actualResults.count)
XCTAssertEqual(2, actualResults.count)

switch actualResults[0] {
case .success(let image):
XCTAssertEqual(64, image.size.width)
XCTAssertEqual(64, image.size.height)
XCTAssertEqual(1200, image.size.width)
XCTAssertEqual(630, image.size.height)
break
case .failure(let error):
XCTFail("unexpected error returned for download: \(error)")
Expand All @@ -82,32 +110,36 @@ class FavIconTests: XCTestCase {
Icon(url: URL(string: "https://google.com/favicon.ico")!, type: .shortcut)
]

var icon = FavIcon.chooseIcon(mixedIcons, width: 50, height: 50)
var sortedIcons = FavIcon.sortIcons(mixedIcons, preferredWidth: 50, preferredHeight: 50)
var icon = sortedIcons[0]

XCTAssertNotNil(icon)
XCTAssertEqual(64, icon!.width)
XCTAssertEqual(64, icon!.height)
XCTAssertEqual(64, icon.width)
XCTAssertEqual(64, icon.height)

icon = FavIcon.chooseIcon(mixedIcons, width: 28, height: 28)
sortedIcons = FavIcon.sortIcons(mixedIcons, preferredWidth: 28, preferredHeight: 28)
icon = sortedIcons[0]

XCTAssertNotNil(icon)
XCTAssertEqual(32, icon!.width)
XCTAssertEqual(32, icon!.height)
XCTAssertEqual(32, icon.width)
XCTAssertEqual(32, icon.height)

icon = FavIcon.chooseIcon(mixedIcons)
sortedIcons = FavIcon.sortIcons(mixedIcons)
icon = sortedIcons[0]

XCTAssertNotNil(icon)
XCTAssertEqual(144, icon!.width)
XCTAssertEqual(144, icon!.height)
XCTAssertEqual(144, icon.width)
XCTAssertEqual(144, icon.height)

icon = FavIcon.chooseIcon(noSizeIcons)
sortedIcons = FavIcon.sortIcons(noSizeIcons)
icon = sortedIcons[0]

XCTAssertNotNil(icon)
XCTAssertEqual(IconType.shortcut.rawValue, icon!.type.rawValue)
XCTAssertEqual(IconType.shortcut.rawValue, icon.type.rawValue)

icon = FavIcon.chooseIcon([])
sortedIcons = FavIcon.sortIcons([])

XCTAssertNil(icon)
XCTAssertEqual(0, sortedIcons.count)
}
}

1 change: 1 addition & 0 deletions Tests/FavIconTests/HTML.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,6 @@
<link rel="shortcut icon"
type="image/png"
href="data:image/png;base64,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">
<meta property="og:image" content="https://www.facebook.com/images/fb_icon_325x325.png">
</head>
</html>

0 comments on commit 833fb33

Please sign in to comment.