Extracting iOS favicon from any website - ios

Most websites have a favicon for iOS in the root directory such as:
https://www.apple.com/apple-touch-icon.png
However, there are a lot of websites where this doesn't work.
I want to be able to download a large favicon (very similar to the one Apple downloads when adding a website to your Home Screen) to my iOS app.
I don't want the smallest favicon which I could do from sites such as:
https://www.google.com/s2/favicons?domain=www.google.com
My intention is not to use Open Graph either, as this gives me the wrong image. An image related to the article for example, when I just wanted the newspapers' logo.
Any help would be greatly appreciated!

you can get favicons from any domain using a hidden google API
here is Swift struct for it:
struct FavIcon {
enum Size: Int, CaseIterable { case s = 16, m = 32, l = 64, xl = 128, xxl = 256, xxxl = 512 }
private let domain: String
init(_ domain: String) { self.domain = domain }
subscript(_ size: Size) -> String {
"https://www.google.com/s2/favicons?sz=\(size.rawValue)&domain=\(domain)"
}
}
then you can use it like this
FavIcon("apple.com")[.l]
you can test them yourself in preview with this SwiftUI code
struct TestFavIconView: View {
let domains = "apple.com,stackoverflow.com,github.com".components(separatedBy: ",")
var body: some View {
List {
ForEach(domains, id:\.self) { domain in
Section {
ForEach(FavIcon.Size.allCases, id:\.rawValue) { size in
HStack {
Text("\(size.rawValue)x\(size.rawValue)").font(.footnote)
Spacer()
AsyncImage(url: URL(string: FavIcon(domain)[size]))
}
}
} header: {
Text(domain)
}
}
}
}
}
struct TestFavIconView_Previews: PreviewProvider {
static var previews: some View {
TestFavIconView()
}
}

Downloading a favicon from any website can be a little bit of a nightmare, as there are multiple "download types" where you can get a favicon from.
A lot of developers place the favicon at the root directory, with the filename of "favicon". However the file could be stored elsewhere, with the location denoted in the HTML header tags. Additionally, there may be multiple different sizes of the favicon, each one having a different "label" in the HTML header tags.
Or they might not be denoted in the HTML header tags, but it might be stored in the web application manifest JSON file, which it itself has it's filename flagged in the HTML header flag.
It can be difficult.
I created an open-source framework that takes a URL as an input, and gets fetches the favicon for you and returns it as a UIImage (if you're on iOS). You can even specify which download type you'd prefer, and which size you'd prefer. If it fails to find your preferred type, it'll fall back and search other methods before failing. You can see the GitHub project here:
https://github.com/will-lumley/FaviconFinder/

yourImageView.sd_setImage(with: URL(string: "https://www.google.com/s2/favicons?sz=64&domain=yourUrl"),
placeholderImage: nil)

Related

SwiftUI Memory leak Issue in ForEach

I have a vertical list in the screen to show the images category wise and each category/list contains list of images which is shown horizontally. (Attached image for reference)
Now when I am scrolling horizontally or vertically then application is crashing due to memory leaking. I guess lots of people facing this issue in the ForEach loop.
I have also try with List instead of ForEach and ScrollView for both vertical/horizontal scrolling but unfortunately facing same issue.
Below code is the main view which create the vertical list :
#ObservedObject var mainCatData = DataFetcher.sharedInstance
var body: some View {
NavigationView {
VStack {
ScrollView(showsIndicators: false) {
LazyVStack(spacing: 20) {
ForEach(0..<self.mainCatData.arrCatData.count, id: \.self) { index in
self.horizontalImgListView(index: index)
}
}
}
}.padding(.top, 5)
.navigationBarTitle("Navigation Title", displayMode: .inline)
}
}
I am using below code to create the horizontal list inside each category, I have used LazyHStack, ForEach loop and ScrollView
#ViewBuilder
func horizontalImgListView(index : Int) -> some View {
let dataContent = self.mainCatData.arrCatData[index]
VStack {
HStack {
Spacer().frame(width : 20)
Text("Category \(index + 1)").systemFontWithStyle(style: .headline, design: .rounded, weight: .bold)
Spacer()
}
ScrollView(.horizontal, showsIndicators: false) {
LazyHStack(spacing: 20) {
ForEach(0..<dataContent.catData.count, id: \.self) { count in
VStack(spacing : 0) {
VStack(spacing : 0) {
if let arrImgNames = themeContent.catData[count].previewImgName {
// Use dynamic image name and it occurs app crash & memory issue and it reached above 1.0 gb memory
Image(arrImgNames.first!).resizable().aspectRatio(contentMode: .fit)
// If I will use the same image name then there is no memory issue and it consumes only 75 mb
// Image("Category_Image_1").resizable().aspectRatio(contentMode: .fit)
}
}.frame(width: 150, height: 325).cornerRadius(8.0)
}
}
}
}
}
}
Below is the data model which I am using to fetch images from json file and shows it in the list
class DataFetcher: ObservableObject {
static let sharedInstance = DataFetcher()
#Published var arrCatData = [CategoryModel]()
init() {
do {
if let bundlePath = Bundle.main.url(forResource: FileName.CategoryData, withExtension: "json"),
let jsonData = try? Data(contentsOf: bundlePath) {
let decodedData = try JSONDecoder().decode([CategoryModel].self, from: jsonData)
DispatchQueue.main.async { [weak self] in
self?.arrCatData = decodedData
}
}
} catch {
print("Could not load \(FileName.CategoryData).json data : \(error)")
}
}
}
struct CategoryModel : Codable , Identifiable {
let id: Int
let catName: String
var catData: [CategoryContentDataModel]
}
struct CategoryContentDataModel : Codable {
var catId : Int
var previewImgName : [String]
}
Crash logs :
malloc: can't allocate region
:*** mach_vm_map(size=311296, flags: 100) failed (error code=3)
(82620,0x106177880) malloc: *** set a breakpoint in malloc_error_break to debug
2021-07-01 18:33:06.934519+0530 [82620:5793991] [framework] CoreUI: vImageDeepmap2Decode() returned 0.
2021-07-01 18:33:06.934781+0530 [82620:5793991] [framework] CoreUI: CUIUncompressDeepmap2ImageData() fails [version 1].
2021-07-01 18:33:06.934814+0530 [82620:5793991] [framework] CoreUI: Unable to decompress 2.0 stream for CSI image block data. 'deepmap2'
(82620,0x106177880) malloc: can't allocate region
:*** mach_vm_map(size=311296, flags: 100) failed (error code=3)
(82620,0x106177880) malloc: *** set a breakpoint in malloc_error_break to debug
Note: All images of category are loading from the assets only and If I will use the static name of the image in the loop then there is no memory pressure and it will consume only 75 mb.
I think there is a image caching issue. Does I have to manage image caching even if I am loading images from assets?
Can anyone assist me to resolve this issue? Any help will be much appreciated. Thanks!!
I faced the same problem when building the app using the SwiftUI framework. I fetched ~600 items from the server (200 ms), then tried to show it in UI using ForEach. But it took 3 GBs of RAM. After research, I understand that it's not an issue of SwiftUI. Memory issue happens because of the loop (for-loop).
I found the following:
In the pre-ARC Obj-C days of manual memory management, retain() and
release() had to be used to control the memory flow of an iOS app. As
iOS's memory management works based on the retain count of an object,
users could use these methods to signal how many times an object is
being referenced so it can be safely deallocated if this value ever
reaches zero.
The following code stays at a stable memory level even though it's looping millions of times.
for _ in 0...9999999 {
let obj = getGiantSwiftClass()
}
However, it's a different story if your code deals with legacy Obj-C code, especially old Foundation classes in iOS. Consider the following code that loads a big image ton of time:
func run() {
guard let file = Bundle.main.path(forResource: "bigImage", ofType: "png") else {
return
}
for i in 0..<1000000 {
let url = URL(fileURLWithPath: file)
let imageData = try! Data(contentsOf: url)
}
}
Even though we're in Swift, this will result in the same absurd memory spike shown in the Obj-C example! The Data init is a bridge to the original Obj-C [NSData dataWithContentsOfURL] -- which unfortunately still calls autorelease somewhere inside of it. Just like in Obj-C, you can solve this with the Swift version of #autoreleasepool; autoreleasepool without the #:
autoreleasepool {
let url = URL(fileURLWithPath: file)
let imageData = try! Data(contentsOf: url)
}
In your case, use autoreleasepool inside ForEach:
ForEach(0..<dataContent.catData.count, id: \.self) { count in
autoreleasepool {
// Code
}
}
References:
https://stackoverflow.com/a/25880106/11009631
https://swiftrocks.com/autoreleasepool-in-swift
Try not using explicit self in your ForEach. I've had some weird leaks in my SwiftUI views and switching to implicit self seemed to get rid of them.
Your main problem is that you're using a ScrollView/VStack vs using a List. List is like UITableView which intelligently only maintains content for cells that are showing. ScrollView doesn't assume anything about the structure and so everything within it is retained. The VStack being lazy only means that it doesn't allocate everything immediately. But as it scrolls to the bottom (or HStack to the side), the memory accumulates because it doesn't release the non visible items
You say you tried List, but what did that code look like? You should have replaced both ScrollView and LazyVStack.
Unfortunately, there is no horizonal list at this moment, so you'll either need to roll your own (perhaps based on UICollectionView), or just minimize the memory footprint of your horizonal rows.
What is the size of your images? Image is smart enough to not need to reload duplicate content: the reason why a single image literal works. But if you're loading different images, they'll all be retained in memory. Having said that, you should be able to load many small preview images. But it sounds like your source images may not be that small.
Try LazyVGrid with only one column instead of using Foreach.
let columns = [GridItem(.flexible(minimum: Device.SCREEN_WIDTH - "Your horizontal padding" , maximum: Device.SCREEN_WIDTH - "Your horizontal padding"))]
ScrollView(.vertical ,showsIndicators: false ){
LazyVGrid(columns: columns,spacing: 25, content: {
ForEach(0..< dataContent.catData.count, id: \.self) { index in
"Your View"
}
}
}

iOS Action Extension, share PDF from Safari fails

I have an Action Extension to which I'm trying to share PDF-files.
I'm using the boilerplate code for ActionRequestHandler.swift that was autogenerated for me:
func beginRequest(with context: NSExtensionContext) {
// Do not call super in an Action extension with no user interface
self.extensionContext = context
for item in context.inputItems as! [NSExtensionItem] {
if let attachments = item.attachments {
for itemProvider in attachments {
...
...
}
}
}
}
Working from other apps
When exporting from every application except Safari, this is what I get:
This is all ok, I can verify that it's an pdf by checking the com.adobe.pdf and then I use the public.file-url to fetch the shared file.
Failing from Safari
But when exporting from Safari (doesn't matter if I choose "Automatic" or "Pdf" for file type), I instead only get com.apple.property-list:
Further info
Both dropbox and OneDrive works, so it's doable in some sort of way.
Also I realised that sharing an PDF from a url that's protected by some sort of login doesn't work with "Public.file-url" since that URL wont be accessible from inside swift-code.
That leads me to think that the java-script preprocessor might be the way to go? Fetch the pdf-contents with JS and pass it on to code?
Question
How do I use the com.apple.property-list to fetch the file?
Or is some config I did faulty, since I get this property-list instead of the pdf/url combo?
While I didn't manage to figure out a solution to the original question, I did manage to solve the problem.
When adding an Action Extension, one gets to choose Action type:
Presents user interface
No user interface
I choosed No user interfacesince that was what I wanted.
That gave me an Action.js file and ActionRequestHandler.swift:
class ActionRequestHandler: NSObject, NSExtensionRequestHandling {
...
}
These files seem to work around a system where the Action.js is supposed to fetch/manipulate the source page and then send information to the backing Swift code. As stated in my original question, when sharing a PDF from Safari, no PDF-URL gets attached.
A working solution
If I instead choose Presents user interface, I got another setup, ActionViewController.swift:
class ActionViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Get the item[s] we're handling from the extension context.
for item in self.extensionContext!.inputItems as! [NSExtensionItem] {
for provider in item.attachments! {
if provider.hasItemConformingToTypeIdentifier(kUTTypePDF as String) {
provider.loadItem(forTypeIdentifier: kUTTypePDF as String, options: nil, completionHandler: { (pdfUrl, error) in
OperationQueue.main.addOperation {
if let pdfUrl = pdfUrl as? URL {
// pdfUrl now contains the path to the shared pdf data
}
}
}
}
}
This file / solution works as expected, the extensionContext gets populated with one attachment that conforms to kUTTypePDF as expected.
Why this works, while the "no gui"-approach doesn't, I have no idea. Bug or feature?
I have not found any documentation of how/why this is supposed to work in Apple's developer section, the "share extension" documentation is very light.

How to use my view controllers and other class in the Share Extension ? iOS | Swift 4

I am creating a chatting application. User can share the images from other application to my application. I have added Share Extension to show my app in the native share app list. I'm also getting the selected data in didSelectPost Method. From here I want to show the list of the users to whom the image can be forwarded. For this, I'm using an already created view controller in the main app target.
override func didSelectPost() {
// This is called after the user selects Post. Do the upload of contentText and/or NSExtensionContext attachments.
if let content = self.extensionContext!.inputItems[0] as? NSExtensionItem {
let contentType = kUTTypeImage as String
// Verify the provider is valid
if let contents = content.attachments as? [NSItemProvider] {
for attachment in contents {
if attachment.hasItemConformingToTypeIdentifier(contentType) {
attachment.loadItem(forTypeIdentifier: contentType, options: nil) { (data, error) in
let url = data as! URL
let imageData = try! Data(contentsOf: url)
// Here I'm navigating to my viewcontroller, let's say: ForwardVC
}
}
}
}
}
I don't want to recreate the same screen in Share Extension. Apart from this view controllers, I have many more classes and wrappers that I want to use within the share extension. Like, SocketManager, Webservices, etc. Please suggest me your approach to achieve the same.
P.S.: I've tried setting multiple targets to required viewControllers and using same pods for Share Extention. In this approach, I'm facing a lot of issues as many of the methods and pods are not extention compliant. Also, is it the right way to do this.

Swift: Is there any way to hide files from users?

I want to know if there is a place in ios devices where I can store video files in a way such that:
1) Users are not able to find video files outside the app.
2) No other app can read the video files.
3) Even jail-broken devices can't access the video files outside the app.
The only way I want these video files to be accessed is through the app only.
Please help me to know where to save the files.
First of all, take a look at link posted by #karthikeyan.
https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html
Choose the right 'SearchPathDomainMask'.
let documents = try! FileManager.default.url(for: .documentDirectory, in: .localDomainMask, appropriateFor: nil, create: false)
From Apple Library:
public struct SearchPathDomainMask : OptionSet {
public init(rawValue: UInt)
public static var userDomainMask: FileManager.SearchPathDomainMask { get }
// user's home directory --- place to install user's personal items (~)
public static var localDomainMask: FileManager.SearchPathDomainMask { get }
// local to the current machine --- place to install items available to everyone on this machine (/Library)
public static var networkDomainMask: FileManager.SearchPathDomainMask { get }
// publically available location in the local area network --- place to install items available on the network (/Network)
public static var systemDomainMask: FileManager.SearchPathDomainMask { get }
// provided by Apple, unmodifiable (/System)
public static var allDomainsMask: FileManager.SearchPathDomainMask { get }
// all domains: all of the above and future items
}

Replace UIWebView's (loaded through a web service) <img> tag source with a local (cached) source

I am working on an app (swift) where i need to load a web page inside UIWebView. Inside that UIWebView there's an <img src="http://www.example.com/uploads/43454.jpg" /> element.
All works fine in this scenario, but the problem is that my 43454.jpg image can be of 5-10 megabytes everytime. So when the UIWebView loads it keeps on loading image for about 2 minutes. Plus this <img> tag can have random sources i.e. 22234.jpg, 98734.jpg, 33123.jpg, and so on.
So to tackle this situation I am trying to use following approach:
List all possible images that we need to show in UIWebView, download and cache them (used Kingfisher library for this purpose) at aplication's startup.
When my UIWebView loads my URL initially, it has nothing in it's <img> elements src attribute, but have a data-image-name="22234.jpg" attribute-value pair.
Now when UIWebView finishes loading its contents, get the image name value from data-image-name attribute.
Check for that image in cache, and update the <img> element's src attribute with that image from cache.
This way UIWebView won't be downloading the image over and over again.
Note: Assuming that UIWebView automatically manages resource cache. All other file types *.js, *.css are being properly cached and not being loaded over and over again. The same doesn't go for images, don't know why.
If this approach seems okay, then how should I accomplish it (Swift 2.2)?
Any quick help will be much appreciated. Thanks
This seems to be the same situation in one of my projects. I had exactly this same scenario. I am going to paste my code here, may be it helps you figure out your solution.
As soon as my app loads I create an array of image URLs and pass it to Kingfisher library to download and cache all images (to disk).
for url in response {
let URL = NSURL(string: url as! String)!
self.PlanImagesArray.append(URL)
}
let prefetcher = ImagePrefetcher(
urls: self.PlanImagesArray,
optionsInfo: nil,
progressBlock: {
(skippedResources, failedResources, completedResources) -> () in
print("progress resources are prefetched: \(completedResources)")
print("progress resources are failedResources: \(failedResources)")
print("progress resources are skippedResources: \(skippedResources)")
},
completionHandler: {
(skippedResources, failedResources, completedResources) -> () in
print("These resources are prefetched: \(completedResources)")
print("These resources are failedResources: \(failedResources)")
print("These resources are skippedResources: \(skippedResources)")
self.activityLoadingIndicator.stopAnimating()
})
prefetcher.start()
At my web view screen I initially loaded my web view and after that used following code to check for particular image in cache and if found converted it into a base64 string and put it back in src attribute of the element.
func webViewDidFinishLoad(webView : UIWebView) {
//UIApplication.sharedApplication().networkActivityIndicatorVisible = false
print("webViewDidFinishLoad")
let xlinkHref = webView.stringByEvaluatingJavaScriptFromString("document.getElementById('background_image').getAttribute('xlink:href')")
//print("xlink:href before = \(webView.stringByEvaluatingJavaScriptFromString("document.getElementById('background_image').getAttribute('xlink:href')"))")
if xlinkHref == "" {
let imageCacheKey = webView.stringByEvaluatingJavaScriptFromString("document.getElementById('background_image').getAttribute('data-cache-key')")
let imageCachePath = ImageCache.defaultCache.cachePathForKey(imageCacheKey! as String)
var strBase64:String = ""
webView.stringByEvaluatingJavaScriptFromString(
"var script = document.createElement('script');" +
"script.type = 'text/javascript';" +
"script.text = \"function setAttributeOnFly(elemName, attName, attValue) { " +
"document.getElementById(elemName).setAttribute(attName, attValue);" +
"}\";" +
"document.getElementsByTagName('head')[0].appendChild(script);"
)!
if(imageCachePath != "") {
ImageCache.defaultCache.retrieveImageForKey(imageCacheKey! as String, options: nil) { (imageCacheObject, imageCacheType) -> () in
let imageData:NSData = UIImageJPEGRepresentation(imageCacheObject!, 100)!
strBase64 = "data:image/jpg;base64," + imageData.base64EncodedStringWithOptions(.EncodingEndLineWithCarriageReturn)
webView.stringByEvaluatingJavaScriptFromString("setAttributeOnFly('background_image', 'xlink:href', '\(strBase64)');")!
}
} else {
webView.stringByEvaluatingJavaScriptFromString("setAttributeOnFly('background_image', 'xlink:href', '\(imageCacheKey)');")!
}
}
Hope it helps.
It sounds like you're basically saying "I know what the images are ahead of time, so I don't want to wait for the UIWebView to load them from the server every time. I want the html to use my local copies instead."
While it's a hack, my first thought was:
Have 2 UIWebViews: one is visible to the user and the other is hidden.
"Trigger" the real page in the hidden UIWebView. When the HTML reaches you...
Create a new string, that is a copy of that HTML, but with the image tags that say <img src="http://... replaced with <img src=\"file://... and pointing to the correct image(s) on disk.
Now tell the visible UIWebView to load that HTML string you built in step 3.

Resources