---
title: "Save PDF to custom data provider on iOS | Nutrient SDK"
canonical_url: "https://www.nutrient.io/guides/ios/save-a-document/to-custom-data-provider/"
md_url: "https://www.nutrient.io/guides/ios/save-a-document/to-custom-data-provider.md"
last_updated: "2026-05-23T00:08:18.127Z"
description: "Save PDF to custom data provider on iOS | guide for Nutrient iOS SDK with detailed instructions and code examples."
---

# Save PDFs to a custom data provider on iOS

Nutrient supports loading data from many different sources. In fact, this can be done from any object that conforms to the [`DataProviding`](https://www.nutrient.io/api/ios/documentation/pspdfkit/dataproviding) protocol, which is known as a data provider. This is especially helpful if you want to support your own encryption or compression scheme.

A data provider is an object that defines how PDF data is read and written from a particular source. This makes reading and writing PDF data customizable, and it gives you the freedom to store your data in the exact way you need it.

## Existing data providers

Nutrient ships with several premade data provider classes:

- [`FileDataProvider`](https://www.nutrient.io/api/ios/documentation/pspdfkit/filedataprovider) reads from and writes to a local file.

- [`CoordinatedFileDataProvider`](https://www.nutrient.io/api/ios/documentation/pspdfkit/coordinatedfiledataprovider) is similar to the above but uses [file coordination](https://www.nutrient.io/../../features/file-coordination). [`Document`](https://www.nutrient.io/api/ios/documentation/pspdfkit/document)s initialized with [`init(url:)`](https://www.nutrient.io/api/ios/documentation/pspdfkit/document/init(url:)) will be backed by a `CoordinatedFileDataProvider` by default.

- [`AESCryptoDataProvider`](https://www.nutrient.io/api/ios/documentation/pspdfkit/aescryptodataprovider) allows you to transparently encrypt and decrypt a given PDF using the [`RNCryptor`](https://github.com/RNCryptor/RNCryptor-Spec) spec.

- [`DataContainerProvider`](https://www.nutrient.io/api/ios/documentation/pspdfkit/datacontainerprovider) is used for reading and writing to a [`Data`](https://developer.apple.com/documentation/foundation/data) object. This is useful if you want to keep your PDF strictly in memory.

## Custom data providers

You can also write your own custom data provider and pass it along to [`init(dataProviders:)`](https://www.nutrient.io/api/ios/documentation/pspdfkit/document/init(dataproviders:)).

### Read support

To provide read support, you have to implement the [`DataProviding`](https://www.nutrient.io/api/ios/documentation/pspdfkit/dataproviding) protocol. It offers methods for reading the data at a specific offset and for uniquely identifying the content. Implementing the more specialized [`FileDataProviding`](https://www.nutrient.io/api/ios/documentation/pspdfkit/filedataproviding) protocol is preferred if your data provider is backed by a file on disk.

Here’s an example of how to implement the [`DataProviding`] protocol to read from a [`Data`](https://developer.apple.com/documentation/foundation/data) instance:

### SWIFT

```swift

class YourDataProvider: NSObject, NSSecureCoding, DataProviding {

    // MARK: Properties

    var size: UInt64 {
        // Returns the size of the data.
        guard let data = self.data else { return 0 }
        return UInt64(data.count)
    }

    var uid: String {
        // This can be anything that uniquely identifies your data:
        // the resource name from the original data, the UUID — you name it.
        return uniqueIdentifierForYourData
    }

    // [...]

    // MARK: DataProviding

    func readData(withSize size: UInt64, atOffset offset: UInt64) -> Data {
        guard let data = self.data else { return Data() }
        // We have to clamp the given size and offset to make sure we don't try
        // to read data that doesn't exist.
        let length = self.size
        let clampedOffset = min(offset, length)
        let clampedSize = min(size, length - clampedOffset)
        // Actually return the data.
        let range: Range = Int(clampedOffset)..<Int(clampedOffset+clampedSize)
        return data.subdata(in: range)
    }
}

```

### OBJECTIVE-C

```objc

@interface YourDataProvider : NSObject <PSPDFDataProviding> @end

@implementation YourDataProvider

#pragma mark - Properties

- (uint64_t)size {
    // Returns the size of the data.
    return self.data.length;
}

- (NSString *)UID {
    // This can be anything that uniquely identifies your data:
    // the resource name from the original data, the UUID — you name it.
    return uniqueIdentifierForYourData;
}

// [...]

#pragma mark - PSPDFDataProviding

- (NSData *)readDataWithSize:(uint64_t)size atOffset:(uint64_t)offset {
    // We have to clamp the given size and offset to make sure we don't try
    // to read data that doesn't exist.
    const NSUInteger length = self.size;
    NSUInteger clampedOffset = MIN((NSUInteger)offset, length);
    NSUInteger clampedSize =  MIN((NSUInteger)size, length - clampedOffset);
    // Actually return the data.
    return [self.data subdataWithRange:NSMakeRange(clampedOffset, clampedSize)];
}

@end

```

### Write support

Write support is a little more difficult to implement; you can’t simply offer a `write` method. The reason for this is that Nutrient actually has to be able to read a document while writing it, which means data can’t just be overwritten.

For this reason, we introduced the concept of [`DataSink`](https://www.nutrient.io/api/ios/documentation/pspdfkit/datasink). It supports the following options ([`DataSinkOptions`](https://www.nutrient.io/api/ios/documentation/pspdfkit/datasinkoptions)):

- [`[]`][pspdfdatasinkoptionnone] (empty set of `DataSinkOptions`) — the incoming writes are from the beginning of the file (this is the default option)

- [`.append`](https://www.nutrient.io/api/ios/documentation/pspdfkit/datasinkoptions/append) — the incoming writes should be _appended_ to the file

To support writing, you first need to write a [`DataSink`](https://www.nutrient.io/api/ios/documentation/pspdfkit/datasink):

### SWIFT

```swift

class YourDataSink: NSObject, DataSink {

    // MARK: Properties

    private(set) var isFinished = false
    let options: DataSinkOptions
    var writtenData = Data()

    // MARK: Lifecycle

    init(options: DataSinkOptions) {
        self.options = options
        super.init()
    }

    // MARK: DataSink

    func write(_ data: Data) -> Bool {
        // We append the passed-in data to our `writtenData`.
        writtenData.append(data)
        return true
    }

    func finish() -> Bool {
        // If you're implementing compression or encryption writing, you might need
        // to tell the compression or encryption library that you're finished
        // writing. You can do this here. For our purposes with the `Data`, we
        // don't need to do anything.
        isFinished = true
        return true
    }
}

```

### OBJECTIVE-C

```objc

@interface YourDataSink : NSObject <PSPDFDataSink>

@property (nonatomic, readonly) PSPDFDataSinkOptions options;
@property (nonatomic, readonly) NSMutableData *writtenData;

@end

@interface YourDataSink ()

@property (nonatomic) BOOL isFinished;

@end

@implementation YourDataSink

- (instancetype)initWithOptions:(PSPDFDataSinkOptions)options {
    if ((self = [super init])) {
        // We initialize `writtenData` with an empty mutable `NSMutableData`.
        _writtenData = [NSMutableData data];
        _options = options;
    }
    return self;
}

- (BOOL)writeData:(NSData *)data {
    // We append the passed-in data to our `writtenData`.
    [self.writtenData appendData:data];
    return YES;
}

- (BOOL)finish {
    // If you're implementing compression or encryption writing, you might need
    // to tell the compression or encryption library that you are finished
    // writing. You can do this here. For our purposes with the `NSData`, we
    // don't need to do anything.
    self.isFinished = YES;
    return YES;
}

@end

```

This is a basic [`DataSink`](https://www.nutrient.io/api/ios/documentation/pspdfkit/datasink) implementation that writes to the passed-in [`Data`](https://developer.apple.com/documentation/foundation/data). For the data provider to make use of it, you have to extend it just a little:

### SWIFT

```swift

class YourDataProvider: NSObject, NSSecureCoding, PSPDFDataProviding {

    //... your previous implementation...

    var additionalOperationsSupported: PSPDFDataProvidingAdditionalOperations {
        // Signal to Nutrient that this data provider can support writing by
        // returning the following option:
        return.write
    }

    func createDataSink(options: PSPDFDataSinkOptions) -> PSPDFDataSink {
        // When Nutrient wants to write to the data provider, it will call this
        // method and it passes in if it wants to overwrite or append to the file.
        return YourDataSink(options: options)
    }

    func replace(with replacementDataSink: PSPDFDataSink) -> Bool {
        // After Nutrient finishes writing, it passes in the data sink
        // that was previously created in `-createDataSinkWithOptions:`.
        let dataSink = replacementDataSink as! YourDataSink

        // We have to check if we have to overwrite or append.
        if dataSink.options.contains(.append) {
            // We have to append the data.
            guard let data = data else { return false }
            var replacementData = data
            replacementData.append(dataSink.writtenData)
        } else {
            // We can simply replace our data.
            data = dataSink.writtenData
        }

        return true
    }
}

```

### OBJECTIVE-C

```objc

@implementation YourDataProvider

//... your previous implementation...

- (PSPDFDataProvidingAdditionalOperations)additionalOperationsSupported {
    // Signal to Nutrient that this data provider can support writing by
    // returning the following option:
    return PSPDFDataProvidingAdditionalOperationWrite;
}

- (id<PSPDFDataSink>)createDataSinkWithOptions:(PSPDFDataSinkOptions)options {
    // When Nutrient wants to write to the data provider, it will call this
    // method and passes in if it wants to overwrite or append to the file.
    return [[YourNSDataSink alloc] initWithOptions:options];
}

- (BOOL)replaceWithDataSink:(id<PSPDFDataSink>)replacementDataSink {
    // After Nutrient finishes writing, it passes in the data sink
    // that was previously created in `-createDataSinkWithOptions:`.
    YourNSDataSink *dataSink = (YourNSDataSink*)replacementDataSink;
    // We have to check if we have to overwrite or append.
    if (dataSink.options & PSPDFDataSinkOptionAppend) {
        // We have to append the data.
        NSMutableData *replacementData = [self.data mutableCopy];
        [replacementData appendData:dataSink.writtenData];
        self.data = replacementData;
    } else {
        // We can simply replace our data.
        self.data = dataSink.writtenData;
    }
    return YES;
}

@end

```

Always remember that even while writing, the data provider must be able to fully read the document.
---

## Related pages

- [Automatically save PDFs on iOS](/guides/ios/features/document-checkpointing.md)
- [Conflict resolution when saving PDFs on iOS](/guides/ios/features/conflict-resolution.md)
- [Incremental PDF saving on iOS](/guides/ios/faq/growing-pdf-file-size.md)
- [Save PDF documents on iOS](/guides/ios/save-a-document.md)
- [How to save PDFs on iOS](/guides/ios/save-a-document/save-as.md)
- [Save PDFs to local storage on iOS](/guides/ios/save-a-document/to-local-storage.md)
- [Save and upload PDFs on iOS devices](/guides/ios/save-a-document/to-remote-server.md)
- [Save PDFs to Document Engine on iOS](/guides/ios/save-a-document/to-document-engine.md)

