Tapsbook iOS SDK Developer Guide

Overview

The tapsBook iOS SDK provides a solution for developers seeking to add a fully integrated photobook creation and print order user experience to their iOS photo apps. The SDK provides the following key functionalities

  1. Creation of multi-page photo book by auto-arrange a set of photos
  2. Full featured page contents customization (layout, background, rich text editing)
  3. Rendering print quality high-res images per page for book print fulfillment

Project Setup

In order to follow this guide, you must have the iOS 7.0 SDK and Xcode 5.0 installed on your system.

Run time requirements

iOS 6.0 or higher

###Configuration

  1. Copy SDK files

    Copy TapsbookSDK.framework and TapsbookSDK.bundle into your project.

    FinderImageForSDK

  2. Link against libiraries

    Check your target's "Link Binary With Libraries" build phase. Make sure your app is being linked against all the following libraries:

    TapsbookSDK.framework
    libsqlite3.0.dylib
    libz.dylib
    UIKit.framework
    Accelerate.framework
    AdSupport.framework
    CoreData.framework
    SystemConfiguration.framework
    ImageIO.framework
    MessageUI.framework
    Social.framework
    Photos.framework
    
  3. Copy resources

    Make sure TapsbookSDK.bundle is included in your target's "Copy Bundle Resources" build phase.

    XcodeImageForCopyBundleResources

  4. Add -ObjC in "Build Settings" > "Other Linker Flags"

  5. Import other 3rd party libraries

    The following 3rd party open source libraries should be also inclued in your project to make the TapsbookSDK work:

    FormatterKit
    Stripe
    PaymentKit
    AsyncDisplayKit
    smc
    AHEasing
    TLLayoutTransitioning
    CocoaLumberjack
    TSMessages
    facebook/pop
    DZNSegmentedControl
    FBKVOController
    Reachability
    BlocksKit
    SDWebImage
    TTTAttributedLabel
    FXBlurView
    AFNetworking 2.0
    GTMBase64
    QiniuSDK
    Google analytics iOS SDK v3
    MZFormSheetController
    fmdb
    PullableView
    FWTPopoverView
    MBProgressHUD
    TTTRandomizedEnumerator
    OBDragDrop
    extobjc
    

    You can get these librarys from Cocoa Pods or github. We also provid a pack of these that we used in the demo project.

    Generally, using a different version of any library above won't make the SDK break down. Just make sure the interfaces of the libraies are not modified. If you find some issue using a specific library, please contact us.

##Concept

  • Album:

    Photo Album; Your app create album by providing photos/images to TapsbookSDK.

  • Page:

    A album created by TapsbookSDK will have some pages, each page have some images and texts arranged in a spesific layout.

  • TBSDKAlbum:

    A class that contains basic information of a album. You pass TBSDKAlbum instance to TapsbookSDK to do some album related oprations.

  • TBImage:

    The image object that TapsbookSDK use. Every photo/image your app provied to TapsbookSDK should be a TBImage instance.

  • Standard Page and Spread Page

    A standard page is a left or right half of a spread page.

##Mechanism of TapsbookSDK

GenreralMechanism

  • Your app tell TapsbookSDK to create a album with some images, and TapsbookSDK will handle page generation, album storage.

  • TapsbookSDK does not store image data for you, you should do this in your app, TapsbookSDK only store necessary infomation (size, path of filesystem, etc) of image.

  • After user check print preview and checkout in TapsbookSDK, you can get each page image for print, and send them to your print server.

##Mechanism of TBImage

  • Every TBImage has a unique identifier, the identifier helps TapsbookSDK to distinguish different images, and tells you which image to load in the dataSource methods.

  • Different sizes of a image will be used by TapsbookSDK in different situation. The thumbnail size (TBImageSize_s) is used in the photo list. The display size (TBImageSize_l) is used in page edit. The full/origin/printing size (TBImageSize_xxl) is used in printing, we will use the name s-size l-size and xxl-size in the following documentation.

  • Save the image data on the file system and set the file path of TBImage(setImagePath:size:). TapsbookSDK will get image data from the file system.

    Note that you should not keep the UIImage instance in memory if it's not necessary, otherwise you may facing out-of-memory issue.

  • After getting the size of the xxl-size image, you should call setXxlSizeInPixel to the TBImage instance. TapsbookSDK use this value to warn user if the image does not have enough pixels to be print clearly in a specific size, we called this low resolution warning.

    It's recommanded to set xxlSizeInPixel when creating TBImage. You can set this value later but TapsbookSDK can show a low resolution warning only after getting this value.

  • There are two ways to download and provide images data to TapsbookSDK

1. **Method 1**: Download all image data (including xxl-size, l-size and s-size image, you can create l-size and s-size image from xxl-size image to avoid downloading them.) to the file system before creating the `TBImage` and pass it to TapsbookSDK.

    * Your user may need to wait for the completion of all the downloading tasks to be able to edit the album.
    * If your user can tolerate the waiting time, and you can keep the image data available in the file system, your implementation will be much simpler, which means you will not need to handle the the image downloading mechanism provided by TapsbookSDK which will be metioned in Method 2.

2. **Method 1.5**: A solution to avoid awaiting a long time at the begnning. Only make sure you have the l-size and s-size image in the file system, and download xxl-size image later when user want to print.

    * Your user still need to wait for a period of time, but not that long as Method 1.
    * You willl need to implement a delegate method to provide your xxl-size image later, details will be shown in the `Usage` part.    

2. **Method 2**: Download specific image data when TapsbookSDK need it.

    * TapsbookSDK does not handle image downloading operation, your app should do it. When TapsbookSDK need a image, it will send you a image request by calling dataSource method(`tbimageRequestImage:size:priority:`). After the downloading is finished, you should tell TapsbookSDK that the image is available(by calling `gotImageWithSize:`).

    * When TapsbookSDK tell dataSource download a image, a priority is provided to let the dataSource know which image should be download first.

    * TapsbookSDK has a preloading mechanism to get better user experience. To achive this, TapsbookSDK will change the priority of a image request (`tbimage:size:priorityOfRequestChangeFrom:to:`). Or even cancel a request (`tbimageCancelImageRequest:size:`)

    * A bunch of image requests may be generated at the same time, your app should maintain a image loading queue to prevent too much image being loaded at the same time which will cause out of memory issue.

    * It's better but not required if your image loading queue can handle cancelation and priority. TapsbookSDK can still work if your queue doesn't support these. To get a good user experience, cancelation is necessary. To get best user experience, priority is necessary.

    * SDK also store the file path you set for the `TBImage` instance, if the path of your image has changed, you can implement the data source method `tbImage:isPathInvalid:ofSize`, and before each time the SDK loading a image to memory, this method will be called to let SDK know whether the path is still valid.

##Usage

###Common Flow

  1. Configuration (Optional if you don't need to change the default behavior)

    1. Create a class that adopts TBSDKConfiguratorProtocol, implement the protocol methods that you want to have a different behavior, here's a sample implementation:

      - (NSDictionary *)basicSettings {
          NSDictionary *basicSettings = @{
                                           kTBSDKBasics : @{
                                                   kTBAppName : [NSNull null],                      // (Required) Your app name, this name will show up in app UI messages
                                                   kTBAppKey : [NSNull null],                       // (Required)
                                                   kTBAppSecret : [NSNull null],                    // (Required)
      
                                                   kTBSupportedRegions : @[             // (Optional) TBSDKRegions, customize the SDK to support multiple ship to regions (countries)
                                                           // TBSDKRegions
                                                           @(TBSDKRegion_UnitedStates)
                                                           ],
      
                                                   kTBMerchantKeys : @{                 // (Required) Your app keys that you setup from http://dashboard.tapsbook.com, Append a string prefix "test_[ACTUAL_KEY]" will connect to the test server
                                                           // Region : merchantKey
                                                           kTBMerchantKeyDefault : [NSNull null],   // (Optional) The default key
                                                           },
                                                   kTBStripeKeys : @{                   // (Required) Stripe uses this key to create a charge token to make the charge on the tapsbook eCommerce server.
                                                           // Region : stripeKey
                                                           kTBStripeKeyDefault : [NSNull null],     // (Optional) The default key
                                                           },
                                                   },
      
                                           kTBAppLogoPaths : @{                         // (Optional) Your app logo that will be printed on the back cover, assuming the back cover has a design include the app logo
                                                   kTBImageSizeS : [NSNull null],
                                                   kTBImageSizeL : [NSNull null],
                                                   kTBImageSizeXXL : [NSNull null],
                                                   },
      
                                           kTBAWSS3 : @{
                                                   kTBAWSS3AppKey : [NSNull null],                  // (Optional) Your AWS s3 cloud storage account, SDK will upload the rendered images to the AWS s3 location, you may need to set your own clean up policy to remove these images routinely
                                                   kTBAWSS3AppSecret : [NSNull null],               // (Optional) Your AWS s3 cloud storage account secret
                                                   kTBAWSS3BucketName : [NSNull null],              // (Optional) AWS uses bucket name to organize your uploaded images, your images will be uploaded to this URL pattern
                                                   },
      
                                           kTBBookGeneration : @{                       // (Optional)
                                                   kTBTemplateDatabaseName : @"TBTemplate_Test_01.sqlite", // (Optional) The name of the template database
                                                   kTBDefaultThemeID : @{
                                                           @(TBProductType_Photobook) : @1,
      
                                                           },
                                                   kTBUseSameBackgroundImageOnTheSameSpread : @{    // (Optional) Retrun YES is you want SDK's page generation use the same background image on the same spread
                                                           @(TBProductType_Photobook) : @NO,
                                                           @(TBProductType_Canvas) : @NO,
                                                           @(TBProductType_Calendar) : @NO,
                                                           @(TBProductType_Card) : @NO,
                                                           },
                                                   kTBMaxNumberofImagesPerPage : @{                 // (Optional)
                                                           @(TBProductType_Photobook) : @4,
                                                           @(TBProductType_Canvas) : @4,
                                                           @(TBProductType_Calendar) : @4,
                                                           @(TBProductType_Card) : @4,
                                                           },
                                                   kTBMinNumberofImagesPerPage : @{                 // (Optional)
                                                           @(TBProductType_Photobook) : @1,
                                                           @(TBProductType_Canvas) : @1,
                                                           @(TBProductType_Calendar) : @1,
                                                           @(TBProductType_Card) : @1,
                                                           },
                                                   kTBMaxNumberofImagesPerSpread : @{               // (Optional)
                                                           @(TBProductType_Photobook) : @4,
                                                           @(TBProductType_Canvas) : @4,
                                                           @(TBProductType_Calendar) : @4,
                                                           @(TBProductType_Card) : @4,
                                                           },
                                                   kTBMinNumberofImagesPerSpread : @{               // (Optional)
                                                           @(TBProductType_Photobook) : @2,
                                                           @(TBProductType_Canvas) : @1,
                                                           @(TBProductType_Calendar) : @1,
                                                           @(TBProductType_Card) : @1,
                                                           },
                                                   kTBAllowAddOrRemovePage : @{                     // (Optional)
                                                           @(TBProductType_Photobook) : @YES,
                                                           @(TBProductType_Canvas) : @NO,
                                                           @(TBProductType_Calendar) : @NO,
                                                           @(TBProductType_Card) : @NO,
                                                           },
                                                   },
      
                                           kTBBehaviorCustomization : @{
                                                   kTBRemindUserToOrderWhenClosingBooks : @NO,      // (Optional) Whether to remind a user they will lose their work in progress if they close.
                                                   kTBEnableAddingText : @NO,                       // (Optional)
                                                   kTBShowOptionsOfBuildingPagesManuallyOrAutomatically : @{  // (Optional)
                                                           @(TBProductType_Photobook) : @NO,
                                                           @(TBProductType_Canvas) : @NO,
                                                           @(TBProductType_Calendar) : @NO,
                                                           @(TBProductType_Card) : @NO,
                                                           },
                                                   kTBUseEmptyTemplateForPageWithNoContent : @NO,   // (Optional)
                                                   kTBLoadProductFromServerWhenPreparingLocalAlbum : @YES,    // (Optional) load prodcut from server, currently should be always be YES
                                                   },
      
                                           kTBCheckoutCustomization : @{                    // (Optional)
                                                   kTBNoCover : @{                                  // (Optional) YES if you don't need a cover or the cover is not customizable
                                                           @(TBProductType_Photobook) : @NO,
                                                           @(TBProductType_Canvas) : @YES,
                                                           @(TBProductType_Calendar) : @YES,
                                                           @(TBProductType_Card) : @YES,
                                                           },
                                                   kTBSendAlbumJSONDictToHostingApp : @NO,  // (Optional) YES when you want to generate page image on your own.
                                                   kTBGeneratePageImagesInDebugMode : @NO,  // (Optional) Helps you debug when kTBSendAlbumJSONDictToHostingApp is YES
                                                   kTBSendOrderInfoToHostingApp     : @NO,  // (Optional) YES when you want to use your checkout method
                                                   },
                                           };
      
          return basicSettings;
      }
      
    2. Register the class in application:didFinishLaunchingWithOptions: of AppDelegate.m:

      // In your appDelegate.m
      
      [TBSDKConfiguration initializeWithConfiguratorClassName:@"EGTBSDKConfiguritor"];
      
    3. Register notification types, TaspbookSDK may send local notifications to tell users keep the application activate while uploading.

      UIUserNotificationSettings *mySettings = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeSound | UIUserNotificationTypeAlert categories:nil];
      [[UIApplication sharedApplication] registerUserNotificationSettings:mySettings];
      
  2. Setup TBSDKAlbumManager before creating/accessing a album.

    [TBSDKAlbumManager sharedInstance].imageDataSource = self;
    [TBSDKAlbumManager sharedInstance].delegate = self;
    
  3. Create some TBImage:

    TBImage *tbImage = [[TBImage alloc] initWithIdentifier:identifier];
    

    If the image is already avaliable in file system:

    [tbImage setImagePath:path size:size];
    

    If you've got the size of the printing-size image:

    [tbImage setXxlSizeInPixel:xxlSize];
    
  4. Create new TBSDKAlbum with images:

    [[TBSDKAlbumManager sharedInstance] createSDKAlbumWithImages:images identifier:identifier title:title tag:tag completionBlock:^(BOOL success, TBSDKAlbum *sdkAlbum, NSError *error) {
        if (success) {
            // Handle success
        }
        else {
            // Handle error
        }
    }];
    

    here:

    • images can be nil, or an arry of TBImage.
    • identifier can be nil or an uniq string to help you recognize your album
    • tag is another property to help you recognize your album, pass 0 if you don't need that value

    You should not create TBSDKAlbum yourself, only TBSDKAlbum instance returned from TapsbookSDK can work properly.

  5. Add images to TBSDKAlbum

    [[TBSDKAlbumManager sharedInstance] addImages:images toSDKAlbum:album completionBlock:nil];
    

    here images is an arry of TBImage.

    TaspbookSDK will recognize duplicate images by the identifier

  6. Open or print a TBSDKAlbum

    BOOL success = [[TBSDKAlbumManager sharedInstance] openSDKAlbum:album presentOnViewController:viewController shouldPrintDirectly:shouldPrintDirectly];
    

    The navigationController you passed to TapsbookSDK should be a full screen UINavigationController. ViewController of TapsbookSDK will be pushed on this navigationController.

  7. Update your sdkAlbum list view/table view/collection view (optional)

  • A callback when user finish editing a sdkAlbum, and press the back button to get back to your viewController from SDK's viewController. You can refresh a specific tableView/collectionView cell because some properties of the sdkAlbum might be modified.

    - (void)albumManager:(TBSDKAlbumManager *)albumManager didFinishEditingSDKAlbum:(TBSDKAlbum *)sdkAlbum {
    
    }
    
  1. Implement xxl size image preloading methods, (This is necessary if you choose to use Method 1.5)

    - (void)albumManager:(TBSDKAlbumManager *)albumManager
    preloadXXLSizeImages:(NSArray *)tbImages
              ofSDKAlbum:(TBSDKAlbum *)sdkAlbum
           progressBlock:(void (^)(NSInteger currentIdx, NSInteger total, float currentProgress))progressBlock
         completionBlock:(void (^)(NSInteger successCount, NSInteger total, NSError *error))completionBlock {
    
        // Create some download tasks to download all the xxl-size images
        // Set the path of xxl-size and xxlSizeInPixel to the TBImage instance 
        // If you can get the downloading progresses, pass the progress to the progressBlock. 
        // If you can't get the downloading progresses, just pass 1 to currentProgress when you've finished downloading a image.
        // Call completionBlock when all the downloading tasks are finished.
        // If some error happen and you cannot download all the images, call completionBlock with the success count and the error you want to show to your user.
    }       
    
    // Optional, sdk will also tell you to stop the downloading tasks if the user give up downloading.
    
    - (void)albumManager:(TBSDKAlbumManager *)albumManager cancelPreloadingXXLSizeImages:(NSArray *)tbImages ofSDKAlbum:(TBSDKAlbum *)sdkAlbum {
    
    }
    
  2. Implement TBImageDataSource methods, (This is necessary only if you choose to use Method 2)

    - (void)tbimageRequestImage:(TBImage *)tbimage size:(TBImageSize)size priority:(TBPriority)priority {
    
        [SomeImageDownloadingQueue downloadImage:tbImage size:size priority:priority completion:^{
            [tbImage gotImageWithSize:size];
        }];
    }
    
    - (void)tbimageCancelImageRequest:(TBImage *)tbimage size:(TBImageSize)size {
        // Cancel a image downloading operation
    }
    
    - (void)tbimage:(TBImage *)tbImage size:(TBImageSize)size priorityOfRequestChangeFrom:(TBPriority)oldPriority to:(TBPriority)newPriority {
        // handle priority change of image request
    }
    
    - (BOOL)tbImage:(TBImage *)tbImage isPathInvalid:(NSString *)path ofSize:(TBImageSize)size {
        if (NOT_VALID) {
            return YES;
        }
    
        return NO;
    }
    

If you can't set xxlSizeInPixel at the beginning

  1. set xxlSizeInPixel for a TBImage before you call [TBImage gotImageWithSize:] as shown below:

    - (void)tbimageRequestImage:(TBImage *)tbimage size:(TBImageSize)size priority:(TBPriority)priority {
    
        [SomeImageDownloadingQueue downloadImage:tbImage size:size priority:priority completion:^{
            [tbImage setXxlSizeInPixel:xxlSize];
            [tbImage gotImageWithSize:size];
        }];
    }
    

Other TBSDKAlbum operations

  1. To get all albums of a tag

    [[TBSDKAlbumManager sharedInstance] allSDKAlbumsOfTag:tag completionBlock:^(BOOL success, NSArray *sdkAlbums, NSError *error) {
        if (success) {
            // handle success
        }
        else {
            // handle error
        }
    }];
    
  2. To rename a album

    [[TBSDKAlbumManager sharedInstance] renameSDKAlbum:album title:title];
    
  3. To remove a album

    [[TBSDKAlbumManager sharedInstance] removeSDKAlbum:album];
    

Usage (Extra)

  1. Add images to a existing album while editing album in SDK

    In TapsbookSDK's Page editing view, there is a optional add photo button on the photo list view. When you implement the delegate method below, this button will show up.

    - (UIViewController *)photoSelectionViewControllerInstanceForAlbumManager:(TBSDKAlbumManager *)albumManager withSDKAlbum:(TBSDKAlbum *)sdkAlbum existingTBImages:(NSArray *)existingTBImages completionBlock:(void (^)(NSArray *newImages))completionBlock
    

    Tapsbook will call this method to get a UIViewController and present it. The work flow of this view controller is designed by you. In general, you will provide some images to your user, mark existing images as selected and cannot be deselected(optional, SDK will ignore duplicated images automatically). Your user may choose some new images and tap a done button, then you should convert these new images to a array of TBImages and call completionBlock(newImages) on main thread to pass new images to TapsbookSDK. After that, these new images can be added to the album book.

Usage (Checkout/Store)

  1. Configuration; In your class that adopts TBSDKConfiguratorProtocol, in basicSettings method, set kTBSendOrderInfoToHostingApp kTBSendAlbumJSONDictToHostingApp: @YES:

    kTBCheckoutCustomization : @{                
            kTBSendAlbumJSONDictToHostingApp : @YES,  // (Optional) YES when you want to generate page image on your own.
            kTBGeneratePageImagesInDebugMode : @NO,  // (Optional) Helps you debug when kTBSendAlbumJSONDictToHostingApp is YES
            kTBSendOrderInfoToHostingApp     : @YES,  // (Optional) YES when you want to use your checkout method
            },
    };
    
  2. Implement TBSDKAlbumManager delegate method:

    - (void)albumManager:(TBSDKAlbumManager *)albumManager printAndCheckoutSDKAlbum:(TBSDKAlbum *)sdkAlbum withInfoDict:(NSDictionary *)infoDict viewControllerToPresentOn:(UIViewController *)viewController {
        CheckoutViewController *vc = [CheckoutViewController new];
        vc.albumInfo = infoDict;
        [viewController presentViewController:vc animated:YES completion:nil];
    }
    

    The CheckoutViewController, is a custom UIViewController where you can have your own checkout flow The infoDict contains album info

DEPRECATED

  1. Provide your product info (If you return YES in useExternalPrintProductInfo):

    - (void)albumManager:(TBSDKAlbumManager *)albumManager loadPrintInfosForSDKAlbum:(TBSDKAlbum *)sdkAlbum standardSize:(CGSize)size completionBlock:(void (^)(NSArray *printInfos))completionBlock {
    
        // 1. Load your print info asynchronously
        // 2. Convert your product info to TBPrintInfo
        // 3. Pass an array of TBPrintInfos to the completionBlock
    }
    

    Here, TBPrintInfo should have these value being set:

* outputType
* name
* previewPath or previewURL
* productID
* price
* stdPagePrintSize
  1. Handle checkout (If you return YES in userExternalCheckout):

    - (void)albumManager:(TBSDKAlbumManager *)albumManager didCheckoutSDKAlbum:(TBSDKAlbum *)sdkAlbum withCheckoutInfo:(NSDictionary *)checkoutInfo {
        [albumManager popAllTBSDKViewControllersAnimated:YES];
    
        NSInteger numberOfPages = [checkoutInfo[kTBSDKCheckoutInfoNumberOfPages] integerValue];
        NSInteger productID = [checkoutInfo[kTBSDKCheckoutInfoProductID] integerValue]; // The product ID tells what kind of product it is.
    
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            for (NSUInteger i = 0; i < numberOfPages; i++) {
                @autoreleasepool {
                    // identifierAndImage is an array @[identifier, image]
                    NSArray *identifierAndImage = [albumManager identifierAndImageForPageOfSDKAlbum:sdkAlbum pageIndex:i];
    
                    // Upload page images and show your own order window
                    [image rpWriteToFile:[NSString stringWithFormat:@"/Users/ultragtx/Desktop/print_%@_%d.jpg", sdkAlbum.title, i] withCompressQuality:1];
                }
            }
        });
    }
    

    The print info dictionary contains the following entities:

* `kTBSDKCheckoutInfoNumberOfPages`, number of pages that the album contains
* `kTBSDKPrintInfoProductID`, the product ID, you can know what kind of cover the album use

</br>
You can get the identifier and image(UIImage *) by calling `[albumManager identifierAndImageForPageOfSDKAlbum:sdkAlbum pageIndex:i]`, here `identifier` is a NSString that can tell you wether the page is modified or not, if the `identifier` is different from the one you get previous, then the page has benn updated and you will need to re-upload the page. If the two identifiers are same, you don't need to upload the same page twice.

**Note**: You should limit the number of page images being loaded (by calling `identifierAndImageForPageOfSDKAlbum:pageIndex:`) at the same time to avoid using too much memory.

##Known Issues

  1. If a xxl-size image is corrupt or incomplete, user can only find this problem in SDK's print preview, and SDK hasn't provied a delegate method to redownload this image. User can only solve this by removing this image from page. This issue will be solved in the future. For now, you'd better check the MD5 of the image to make sure it's not corrupted.