iOS Offline Caching for Web Content

Related tags

Cache mattress
Overview

Mattress

A Swift framework for storing entire web pages into a disk cache distinct from, but interoperable with, the standard NSURLCache layer. This is useful for both pre-caching web content for faster loading, as well as making web content available for offline browsing.

Requirements

  • iOS 7.0+ (iOS 8 required for integration as an embedded framework)

Installation

Mattress includes a wrapper around CommonCrypto so that it can be easily used from within Swift. You will need to make sure you include both the Mattress and CommonCrypto frameworks in your project.

Carthage (Recommended)

If you are not already using Carthage, you will need to install it using Homebrew.

$ brew update
$ brew install carthage

Once installed, add it to your Cartfile:

github "buzzfeed/Mattress" >= 1.0.0

You will then need to build using Carthage, and manually integrate both the Mattress and CommonCrypto frameworks into your project.

$ carthage build

CocoaPods

If you are not already using CocoaPods, you will need to install it using RubyGems.

$ gem install cocoapods

Once installed, add it to your Podfile:

pod 'Mattress', '~> 1.0.0'

Manual

  1. Open the Mattress folder, and drag Mattress.xcodeproj into the file navigator of your app project. NOTE: The Mattress project needs to be added somewhere under the target project or you won't be able to add it to your target dependencies.
  2. Ensure that the deployment target of the Mattress project matches that of the application target.
  3. In your target's "Build Phases" panel, add Mattress.framework to the "Target Dependencies"
  4. Click on the + button at the top left of the panel and select "New Copy Files Phase". Rename this new phase to "Copy Frameworks", set the "Destination" to "Frameworks", and add both Mattress.framework and CommonCrypto.framework.

Usage

You should create an instance of URLCache and set it as the shared cache for your app in your application:didFinishLaunching: method.

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
    let kB = 1024
    let MB = 1024 * kB
    let GB = 1024 * MB
    let isOfflineHandler: (() -> Bool) = {
        // This is for demonstration only.
        // You should use Reachability or your preferred method to determine offline status
        // and return a proper value here.
        return false
    }
    let urlCache = Mattress.URLCache(memoryCapacity: 20 * MB, diskCapacity: 20 * MB, diskPath: nil,
    	mattressDiskCapacity: 1 * GB, mattressDiskPath: nil, mattressSearchPathDirectory: .DocumentDirectory,
    	isOfflineHandler: isOfflineHandler)
    
    NSURLCache.setSharedURLCache(urlCache)
    return true
}

To cache a webPage in the Mattress disk cache, simply call URLCache's diskCacheURL:loadedHandler: method.

NSLog("Caching page")
let urlToCache = NSURL(string: "https://www.google.com")
if let
    cache = NSURLCache.sharedURLCache() as? Mattress.URLCache,
    urlToCache = urlToCache
{
    cache.diskCacheURL(urlToCache, loadedHandler: { (webView) -> (Bool) in
            /*
               Note: The below code should work for fairly simple pages. However, if the page you are
               attempting to cache contains progressively / lazily loaded images or other assets you
               will also need to trigger any JavaScript functions here to mimic user actions and
               ensure those assets are also loaded before returning true.
            */
            let state = webView.stringByEvaluatingJavaScriptFromString("document.readyState")
            if state == "complete" {
                // Loading is done once we've returned true
                return true
            }
            return false
        }, completeHandler: { () -> Void in
            NSLog("Finished caching")
        }, failureHandler: { (error) -> Void in
            NSLog("Error caching: %@", error)
    })
}

Once cached, you can simply load the webpage in a UIWebView and it will be loaded from the Mattress cache, like magic.

Example

To run the example, please use the following steps.

  1. Open the Mattress workspace
  2. Run the MattressExample scheme
  3. Tap "Cache Page"
  4. Wait for the log message stating caching has finished
  5. Disable internet access
  6. Tap "Load Page" - the page will load via Mattress

Considerations

Mattress does not work with WKWebView. The current WKWebView implementation uses its own internal system for caching and does not properly integrate with NSURLProtocol to allow Mattress to intercept requests made.

Due to Mattress' current architecture and use of web views, a good chunk of the caching work must happen on the main thread. This is obviously not a problem when caching pages while in the background, such as during a background fetch. However, it is something to be mindful of when your app is active in the foreground. We have had good luck using it this way with minimal performance impact, but your mileage may vary.

Contributing

Contributions are welcome. Please feel free to open a pull request.

We also welcome feature requests and bug reports. Just open an issue.

License

Mattress is licensed under the MIT License.

Comments
  • Add dependeny and update documenation

    Add dependeny and update documenation

    Hi, could you please add Reachability as a dependency and/ or write this in the documentation? I added pod 'ReachabilitySwift', '~> 2.1' to my podfile and needed to update your example to the code below:

    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
    
            setupCaching()
    
            return true
        }
    
        private func setupCaching(){
    
            do{
                let reach = try Reachability.reachabilityForInternetConnection()
                try reach.startNotifier()
                let kB = 1024
                let MB = 1024 * kB
                let GB = 1024 * MB
                let isOfflineHandler: (() -> Bool) = {
                    let isOffline = reach.currentReachabilityStatus == Reachability.NetworkStatus.NotReachable
                    return isOffline
                }
                let urlCache = Mattress.URLCache(memoryCapacity: 20 * MB, diskCapacity: 20 * MB, diskPath: nil,
                    mattressDiskCapacity: 1 * GB, mattressDiskPath: nil, mattressSearchPathDirectory: .DocumentDirectory,
                    isOfflineHandler: isOfflineHandler)
    
                NSURLCache.setSharedURLCache(urlCache)
            }
            catch let err as NSError {
                print("Error with caching: " + err.localizedDescription)
            }
        }
    
    opened by ogezue 9
  • Caching with different states

    Caching with different states

    I have recognized that a page often ends loading with a different state. I printing the line let state = webView.stringByEvaluatingJavaScriptFromString("document.readyState") to the console. Sometimes it finishes with "interactive" and sometimes with "completed" when loading the same url. Should the caching process then be repeated?

    I tried to cache different pages consequently by calling.

    cachePage(SiteA)
    cachePage(SiteB)
    cachePage(SiteC)
    

    Is it possible that I have to wait until the former caching process has been completed? What I'm trying to achieve is that my apps caches certain pages after initialization to present them as fast as possible when the user visits them.

    Another question: Does the caching mechanism needs an instance of a webView in my class or can it cache independently, so that I can put cachePage in it's own model class?

        func cachePage(url: NSURL) {
            log.info("Caching page \(url.absoluteString)")
            if let cache = NSURLCache.sharedURLCache() as? Mattress.URLCache
            {
                cache.diskCacheURL(url, loadedHandler: { (webView) -> (Bool) in
                        let state = webView.stringByEvaluatingJavaScriptFromString("document.readyState")
                        log.verbose("state: \(state)")
                        return state == "complete"
                    }, completeHandler: { () -> Void in
                        log.info("Finished caching \(url.absoluteString)")
                    }, failureHandler: { (error) -> Void in
                        log.error("Error caching: \(error.localizedDescription)")
                })
            }
        }
    
    opened by ogezue 7
  • Plans to support Objective C Projects

    Plans to support Objective C Projects

    Hi - i currently have a big Objective C Project that will not be migrated to Swift. It has been migrated to iOS9 as minimum deployment target.

    Now I imported Mattress via cocoapods and and added the use_frameworks! tag. This causes problems with other imported libraries.

    Have you planned to implement support for Objective C that is compatible to other library? Another point is that I get a warning regarding deprecation of NSURLConnection in your lib with iOS9.

    opened by ogezue 5
  • Convert to Swift 3, some naming changes

    Convert to Swift 3, some naming changes

    I found that this is a working solution for #40 not sure how the merging process will affect the current pod, but after testing this fork out it seemed to work fine in Swift 3.

    opened by abanobmikaeel 3
  • Do not store responses with >= 400 responseCode

    Do not store responses with >= 400 responseCode

    @lordkev

    I keep seeing this issue show up where in offline mode it just immediately loads a white page. I think it's failing and then saving that failure for later so we just keep getting it.

    opened by dmauro 3
  • Added additional documentation and cleaned up existing doc

    Added additional documentation and cleaned up existing doc

    @dmauro @sharpfive

    Let me know if you see anything missing or if you think something's overkill. I leaned on the side of thoroughness even if some things were likely obvious anyway.

    opened by lordkev 3
  • FeatureRequest: Ship cached websites with app store build

    FeatureRequest: Ship cached websites with app store build

    I'm creating a conference app with an integration of some websites for venue, sponsors etc. This pages do not change quite often and it would be great to ship them cached with the app store build.

    Some users start the app at the conference center for the first time where wifi conditions are sometimes very poor. Instead of showing a blank page the users should see at least "something".

    It would be great if mattress could support this. What I currently would do is caching the websites within the simulator, cache the contents of the required pages and zip the mattress folder afterwards to ship it with the app (Currently there is a bug, see https://github.com/buzzfeed/mattress/issues/33).

    Is there something that can be solved by mattress to improve this process? For example that mattress could create the zip-file and present a mail dialog to bring the file to the devs machine? Or to update the cache automatically before creating the app store build? Will the cache be working on other devices?

    opened by ogezue 2
  • Use of unresolved identifier 'Mattress'

    Use of unresolved identifier 'Mattress'

    Hi, after installation via cocoa pods this line let urlCache = Mattress.URLCache(memoryCapacity: 20 * MB, diskCapacity: 20 * MB, diskPath: nil, mattressDiskCapacity: 1 * GB, mattressDiskPath: nil, mattressSearchPathDirectory: .DocumentDirectory, isOfflineHandler: isOfflineHandler)

    shows an error Use of unresolved identifier 'Mattress'

    opened by aetsyss 2
  • Use CommonCrypto instead of CryptoSwift

    Use CommonCrypto instead of CryptoSwift

    Based this on the following SO answers. It uses a dummy framework to import CommonCrypto so that the end user of Mattress doesn't have to add the module map to their own project.

    http://stackoverflow.com/questions/24123518/how-to-use-cc-md5-method-in-swift-language

    http://stackoverflow.com/questions/25248598/importing-commoncrypto-in-a-swift-framework

    opened by lordkev 2
  • storing multiple pages

    storing multiple pages

    I need to save multiple pages as user navigate through them, in example and in wiki you have used only 1 url should i use different database to store requested pages?

    opened by tvstuff 1
  • Fix disk path

    Fix disk path

    Looks like there was a trailing slash missing that was causing all files to be stored in the root Documents directory.

    Per issue https://github.com/buzzfeed/mattress/issues/33.

    @dmauro

    opened by lordkev 1
  • Issue using Mattress with CocoaPods & Swift 2.3

    Issue using Mattress with CocoaPods & Swift 2.3

    When including Mattress via CocoaPods and manually specifying the SWIFT_VERSION to 2.3, I get a compilation error with DiskCache.diskPath() (line 457):

    if !NSFileManager.defaultManager().fileExistsAtPath(fileURL.absoluteString, isDirectory: &isDir) {
    ...
    }
    

    The issue stems from the fact that NSFileManager.fileExistsAtPath(...) accepts a non-optional string, so fileURL.absoluteString has to be unwrapped.

    I pushed a fix to my own fork and raised a PR - just not too sure if my solution is the most ideal given the rest of the context of DiskCache. Let me know what you think 👍

    opened by erawhctim 0
  • Fix absolute file path error in `DiskCache` - Swift 2.3

    Fix absolute file path error in `DiskCache` - Swift 2.3

    Added the absolute file path as a separate variable in the let expression so its value can be safely unwrapped and used within NSFileManager.fileExistsAtPath(...) (in order for Mattress to compile properly in Swift 2.3).

    opened by erawhctim 1
  • DictionaryKey for DiskCache's current cache size mislabled

    DictionaryKey for DiskCache's current cache size mislabled

    It shouldn't affect functionality, but the key used to store our current cache size is labeled as "maxCacheSize", which it is not. The maxCacheSize gets passed in on initialization each time. We should rename that, and while we're at it, go ahead and capitalize those DictionaryKey constants for style.

    opened by dmauro 0
Owner
BuzzFeed
BuzzFeed
A caching and consistency solution for immutable models.

?? Data Rocket Data is a model management system with persistence for immutable models. Motivation Immutability has many benefits, but keeping models

Peter Livesey 652 Nov 11, 2022
Melodic Caching for Swift

Johnny is a generic caching library written for Swift 4. Features Johnny can cache any model object that conforms to the Cachable protocol. Out-of-the

Zoltán Matók 37 Nov 21, 2022
Swift caching library

Cache A generic caching library for Swift. Cache depends on Foundation. This is still very much a work in progress. Usage Cache provides a simple Cach

Sam Soffes 210 Sep 9, 2022
Apple Asset Cache (Content Cache) Tools

AssetCacheTool A library and tool for interacting with both the local and remote asset caches. This is based on research I did a few years ago on the

Kenneth Endfinger 21 Jan 5, 2023
MrCode is a simple GitHub iPhone App that can cache Markdown content (include images in HTML) for read it later.

MrCode is a simple GitHub iPhone App that can cache Markdown content (include images in HTML) for read it later.

hao 448 Dec 19, 2022
EVURLCache - a NSURLCache subclass for handling all web requests that use NSURLRequest

EVURLCache What is this? This is a NSURLCache subclass for handeling all web requests that use NSURLRequest. (This includes UIWebView) The EVURLCache

Edwin Vermeer 296 Dec 18, 2022
Cachyr A typesafe key-value data cache for iOS, macOS, tvOS and watchOS written in Swift.

Cachyr A typesafe key-value data cache for iOS, macOS, tvOS and watchOS written in Swift. There already exists plenty of cache solutions, so why creat

Norsk rikskringkasting (NRK) 124 Nov 24, 2022
Carlos - A simple but flexible cache, written in Swift for iOS 13+ and WatchOS 6 apps.

Carlos A simple but flexible cache, written in Swift for iOS 13+ and WatchOS 6 apps. Breaking Changes Carlos 1.0.0 has been migrated from PiedPiper de

National Media & Tech 628 Dec 3, 2022
A lightweight generic cache for iOS written in Swift with extra love for images.

Haneke is a lightweight generic cache for iOS and tvOS written in Swift 4. It's designed to be super-simple to use. Here's how you would initalize a J

Haneke 5.2k Dec 29, 2022
High performance cache framework for iOS.

YYCache High performance cache framework for iOS. (It's a component of YYKit) Performance You may download and compile the latest version of sqlite an

null 2.3k Dec 16, 2022
Everyone tries to implement a cache at some point in their iOS app’s lifecycle, and this is ours.

Everyone tries to implement a cache at some point in their app’s lifecycle, and this is ours. This is a library that allows people to cache NSData wit

Spotify 1.2k Dec 28, 2022
Fast, non-deadlocking parallel object cache for iOS, tvOS and OS X

PINCache Fast, non-deadlocking parallel object cache for iOS and OS X. PINCache is a fork of TMCache re-architected to fix issues with deadlocking cau

Pinterest 2.6k Dec 28, 2022
Delightful framework for iOS to easily persist structs, images, and data

Installation • Usage • Debugging • A Word • Documentation • Apps Using Disk • License • Contribute • Questions? Disk is a powerful and simple file man

Saoud Rizwan 3k Jan 3, 2023
WireGuard for iOS and macOS

WireGuard for iOS and macOS This project contains an application for iOS and for macOS, as well as many components shared between the two of them. You

WireGuard 608 Dec 28, 2022
Transcription Helper - iOS application for assisting in transcribing audio files

Transcription Helper This is an iOS application written in Objective-c for assisting the people who want to work out a piece of audio, in order to wri

Soheil Novinfard 7 Oct 26, 2022
Vellum is local persistent data storage for iOS

Vellum Requirements Swift 5.0 or higher iOS 9.3 or higher Installation Cocoapods Vellum is available through CocoaPods. To install it, simply add the

Nayanda Haberty 16 Dec 8, 2021
ASDebugger is a remote debugging toolset for iOS App.

ASDebugger ASDebugger is a remote debugging toolset for iOS App. it's a way remotely check any network transaction, effortlessly Mock Data, It is able

App Scaffold 10 Sep 2, 2021
Get more reviews in your iOS app.

Remotely-configurable chatbots & chat flows for iOS ChatKit is a remotely-configurable iOS library designed to help you build iMessage-esque chat flow

Zachary Shakked 3 Dec 14, 2022
iOS Swift Pazarama Bootcamp Week 3 task - App by builded with iTunes Api

MeTunes! a library for itunes! MeTunes. Key Features • Installation • Download • Technologies Used • Credits • License Key Features Fully Programmatic

Asım Can Yağız 2 Oct 14, 2022