Tag Archives: wordpress

Objective-c tutorial – adding coredata to save posts and comments

Hint (before starting the coredata part of this tutorial): This tutorial is based on the first two parts, see the first part here and the second part here. You should have them done before starting with this part.

Now we are going to add coredata to the project. This way posts and comments will be saved in the coredata database. Additionally, we will add a button to delete the contents of the database and another button to sync the data between the blog and coredata.

Adding coredata to the project

Usually there is an option to use coredata while adding a project in Xcode. Since we already have a project, we need to add coredata the following way: Select your project from the files, click the tab “Build Phases” and open “Link Binary With Libraries”. There is a button representing a “+” which you should click:

Adding coredata to the project

Adding coredata to the project

After this a dialog opens, search for coredata, select CoreData.framework and click “Add”:

Adding coredata to the project - step 2

Adding coredata to the project – step 2

Now you should add the model file of coredata to the project. Click “File” -> “New” -> “File”, select the register “Core Data” and choose the first option called “Data Model”:

Add the coredata data model

Add the coredata data model

Click “Next” and choose a name (I chose “wpconnect.xcdatamodeld”).

Defining the coredata data model

Open the data model you just added to the project. In the bottom right corner you will see two options: “Editor” and “Style”. Open the “Editor” to mode. Use the button “Add Entity” to add two entities, call the first one “Post” and the second one “PostComment”. For the entity “Post”, add an attribute “postId” with the type “Integer 16” and another attribute “title” with the type “String”. Add a relationship called “comments” with the destination “PostComment”. After clicking the relationship “comments”, you will see an relationship inspector on the right side. Set type to “To Many” (because a post has 0..* comments).

For the entity “PostComment”, add an attribute “comId” with the type “Integer 16” and another attribute “text” with the type “String”. Add a relationship “post” and set the destination to “Post”. The type needs to stay “To One”, because a comment always belongs to a post.

New model classes from coredata

During the previous two steps of the tutorial we already created two model classes: Post and PostComment. Delete these four files (both header and implementation). We will generate these classes via the coredata framework. Click on “File” -> “New” -> “File” and choose “NSManagedObject subclass” from the Core Data register:

Add the model classes via coredata

Add the model classes via coredata

In the consecutive views, select the data model (wpconnect in our case) and the two classes “Post” and “PostComment”. After generating, the classes should look like this:

Post.h

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>

@class PostComment;

@interface Post : NSManagedObject

@property (nonatomic, retain) NSNumber * postId;
@property (nonatomic, retain) NSString * title;
@property (nonatomic, retain) NSSet *comments;
@end

@interface Post (CoreDataGeneratedAccessors)

- (void)addCommentsObject:(PostComment *)value;
- (void)removeCommentsObject:(PostComment *)value;
- (void)addComments:(NSSet *)values;
- (void)removeComments:(NSSet *)values;

@end

Post.m

#import "Post.h"
#import "PostComment.h"

@implementation Post

@dynamic postId;
@dynamic title;
@dynamic comments;

@end

PostComment.h

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>

@class Post;

@interface PostComment : NSManagedObject

@property (nonatomic, retain) NSNumber * comId;
@property (nonatomic, retain) NSString * text;
@property (nonatomic, retain) Post *post;

@end

PostComment.m

#import "PostComment.h"
#import "Post.h"

@implementation PostComment

@dynamic comId;
@dynamic text;
@dynamic post;

@end

Further implementation for coredata: Since we added coredata to an existing project, we need to adjust also both AppDelegate files. In the header file (AppDelegate.h) add the following properties:

@property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;
@property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;
@property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;

In the implementation file (AppDelegate.m) add the following lines before “@end”:

#pragma mark - Core Data stack

@synthesize persistentStoreCoordinator = _persistentStoreCoordinator;
@synthesize managedObjectModel = _managedObjectModel;
@synthesize managedObjectContext = _managedObjectContext;

- (NSURL *)applicationDocumentsDirectory {
    // The directory the application uses to store the Core Data store file. This code uses a directory named "me.meberhard.AnotherText" in the user's Application Support directory.
    NSURL *appSupportURL = [[[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory inDomains:NSUserDomainMask] lastObject];
    return [appSupportURL URLByAppendingPathComponent:@"me.meberhard.WordpressConnect"];
}

- (NSManagedObjectModel *)managedObjectModel {
    // The managed object model for the application. It is a fatal error for the application not to be able to find and load its model.
    if (_managedObjectModel) {
        return _managedObjectModel;
    }

    NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"wpconnect" withExtension:@"momd"];
    _managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
    return _managedObjectModel;
}

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
    // The persistent store coordinator for the application. This implementation creates and return a coordinator, having added the store for the application to it. (The directory for the store is created, if necessary.)
    if (_persistentStoreCoordinator) {
        return _persistentStoreCoordinator;
    }

    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSURL *applicationDocumentsDirectory = [self applicationDocumentsDirectory];
    BOOL shouldFail = NO;
    NSError *error = nil;
    NSString *failureReason = @"There was an error creating or loading the application's saved data.";

    // Make sure the application files directory is there
    NSDictionary *properties = [applicationDocumentsDirectory resourceValuesForKeys:@[NSURLIsDirectoryKey] error:&error];
    if (properties) {
        if (![properties[NSURLIsDirectoryKey] boolValue]) {
            failureReason = [NSString stringWithFormat:@"Expected a folder to store application data, found a file (%@).", [applicationDocumentsDirectory path]];
            shouldFail = YES;
        }
    } else if ([error code] == NSFileReadNoSuchFileError) {
        error = nil;
        [fileManager createDirectoryAtPath:[applicationDocumentsDirectory path] withIntermediateDirectories:YES attributes:nil error:&error];
    }

    if (!shouldFail && !error) {
        NSPersistentStoreCoordinator *coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
        NSURL *url = [applicationDocumentsDirectory URLByAppendingPathComponent:@"OSXCoreDataObjC.storedata"];
        if (![coordinator addPersistentStoreWithType:NSXMLStoreType configuration:nil URL:url options:nil error:&error]) {
            coordinator = nil;
        }
        _persistentStoreCoordinator = coordinator;
    }

    if (shouldFail || error) {
        // Report any error we got.
        NSMutableDictionary *dict = [NSMutableDictionary dictionary];
        dict[NSLocalizedDescriptionKey] = @"Failed to initialize the application's saved data";
        dict[NSLocalizedFailureReasonErrorKey] = failureReason;
        if (error) {
            dict[NSUnderlyingErrorKey] = error;
        }
        error = [NSError errorWithDomain:@"YOUR_ERROR_DOMAIN" code:9999 userInfo:dict];
        [[NSApplication sharedApplication] presentError:error];
    }
    return _persistentStoreCoordinator;
}

- (NSManagedObjectContext *)managedObjectContext {
    // Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.)
    if (_managedObjectContext) {
        return _managedObjectContext;
    }

    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
    if (!coordinator) {
        return nil;
    }
    _managedObjectContext = [[NSManagedObjectContext alloc] init];
    [_managedObjectContext setPersistentStoreCoordinator:coordinator];

    return _managedObjectContext;
}

#pragma mark - Core Data Saving and Undo support

- (IBAction)saveAction:(id)sender {
    // Performs the save action for the application, which is to send the save: message to the application's managed object context. Any encountered errors are presented to the user.
    if (![[self managedObjectContext] commitEditing]) {
        NSLog(@"%@:%@ unable to commit editing before saving", [self class], NSStringFromSelector(_cmd));
    }

    NSError *error = nil;
    if ([[self managedObjectContext] hasChanges] && ![[self managedObjectContext] save:&error]) {
        [[NSApplication sharedApplication] presentError:error];
    }
}

- (NSUndoManager *)windowWillReturnUndoManager:(NSWindow *)window {
    // Returns the NSUndoManager for the application. In this case, the manager returned is that of the managed object context for the application.
    return [[self managedObjectContext] undoManager];
}

- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender {
    // Save changes in the application's managed object context before the application terminates.

    if (!_managedObjectContext) {
        return NSTerminateNow;
    }

    if (![[self managedObjectContext] commitEditing]) {
        NSLog(@"%@:%@ unable to commit editing to terminate", [self class], NSStringFromSelector(_cmd));
        return NSTerminateCancel;
    }

    if (![[self managedObjectContext] hasChanges]) {
        return NSTerminateNow;
    }

    NSError *error = nil;
    if (![[self managedObjectContext] save:&error]) {

        // Customize this code block to include application-specific recovery steps.
        BOOL result = [sender presentError:error];
        if (result) {
            return NSTerminateCancel;
        }

        NSString *question = NSLocalizedString(@"Could not save changes while quitting. Quit anyway?", @"Quit without saves error question message");
        NSString *info = NSLocalizedString(@"Quitting now will lose any changes you have made since the last successful save", @"Quit without saves error question info");
        NSString *quitButton = NSLocalizedString(@"Quit anyway", @"Quit anyway button title");
        NSString *cancelButton = NSLocalizedString(@"Cancel", @"Cancel button title");
        NSAlert *alert = [[NSAlert alloc] init];
        [alert setMessageText:question];
        [alert setInformativeText:info];
        [alert addButtonWithTitle:quitButton];
        [alert addButtonWithTitle:cancelButton];

        NSInteger answer = [alert runModal];

        if (answer == NSAlertFirstButtonReturn) {
            return NSTerminateCancel;
        }
    }

    return NSTerminateNow;
}

These lines are usually auto-generated when you add an application with coredata.

Further implementation – post coredata

After taking care of adding the coredata framework, the data model, the model classes and adjusting AppDelegate, we can now adjust the implementation. We assume the following workflow:

  1. The application has two tables, one displays the posts and the other displays comments on posts.
  2. We have two buttons: One for syncing the coredata database with the blog, and another one to wipe the coredata database.
  3. When the application start, show all posts which are currently available in the coredata database.
  4. When the user clicks syncs, download new posts and comments from the blog (we will not add logic for updating changed posts or for deleting not available posts – in order to keep it simple).
  5. When the user deletes the contents of the coredata database, the table views will be empty, because there are no posts and comments available anymore.

The logic in AppDelegate.m:

#import "AppDelegate.h"
#import "Post.h"
#import "PostComment.h"

@interface AppDelegate ()

@end

@implementation AppDelegate

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    // Insert code here to initialize your application

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(flushData) name:@"flushData" object:nil];

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(syncData) name:@"syncData" object:nil];

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(getCommentsForPostId:) name:@"getComments" object:nil];

    [self showPostsInView];
    }

- (void)applicationWillTerminate:(NSNotification *)aNotification {
    // Insert code here to tear down your application
}

- (void)syncData {
    [self receivePostsFromBlog];
}

- (void)receivePostsFromBlog {
    NSURL *url = [NSURL URLWithString:@"http://meberhard.me/wp-json/posts"];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *repsone, NSData *data, NSError *connectionError) {
        if (data.length > 0 && connectionError == nil) {
            NSDictionary *wpPosts = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
            for (id key in wpPosts) {
                NSNumber *postId = [NSNumber numberWithInt:((int)[[key objectForKey:@"ID"] integerValue])];
                if (![self checkIfPostIdExists:postId]) {
                    NSString *postTitle = [key objectForKey:@"title"];
                    NSManagedObjectContext *context = [self managedObjectContext];
                    Post *post = [NSEntityDescription insertNewObjectForEntityForName:@"Post" inManagedObjectContext:context];
                    post.postId = postId;
                    post.title = postTitle;
                    NSError *error;
                    if (![context save:&error]) {
                        NSLog(@"Something went wrong: %@", [error localizedDescription]);
                    } else {
                        [self receiveCommentsFromBlogForPost:post];
                    }
                } else {
                    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
                    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Post" inManagedObjectContext:[self managedObjectContext]];
                    [fetchRequest setEntity:entity];
                    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"postId == %@", postId];
                    [fetchRequest setPredicate:predicate];
                    NSError *error;
                    NSArray *items = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
                    Post *post = [items objectAtIndex:0];
                    [self receiveCommentsFromBlogForPost:post];
                    NSLog(@"Post with id %@ exists in DB, skipping", postId);
                }
            }
        }
        [self showPostsInView];
    }];
}

- (void)receiveCommentsFromBlogForPost:(Post *)post {
    NSString *restUrl = [NSString stringWithFormat:@"http://meberhard.me/wp-json/posts/%ld/comments", [post.postId integerValue]];
    NSURL *url = [NSURL URLWithString:restUrl];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
        if (data.length > 0 && connectionError == nil) {
            NSDictionary *wpComments = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
            for (id key in wpComments) {
                NSNumber *commentId = [NSNumber numberWithInt:((int)[[key objectForKey:@"ID"] integerValue])];
                if (![self checkIfCommentIdExists:commentId]) {
                    NSMutableString *commentText = [[NSMutableString alloc] init];
                    [commentText appendString:[key objectForKey:@"content"]];

                    NSManagedObjectContext *context = [self managedObjectContext];
                    PostComment *pcom = [NSEntityDescription insertNewObjectForEntityForName:@"PostComment" inManagedObjectContext:context];
                    pcom.comId = commentId;
                    pcom.text = commentText;
                    pcom.post = post;
                    [post addCommentsObject:pcom];
                    NSError *error;
                    if (![context save:&error]) {
                        NSLog(@"Something went wrong: %@", [error localizedDescription]);
                    }
                } else {
                    NSLog(@"Comment with comment id %@ exists, skipping", commentId);
                }
            }
        }
    }];
}

- (void)showPostsInView {
    NSLog(@"starting with showPostsInView");
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Post" inManagedObjectContext:[self managedObjectContext]];
    [fetchRequest setEntity:entity];
    NSError *error;
    NSArray *fetchedObjects = [[self managedObjectContext] executeFetchRequest:fetchRequest error:&error];
    [[NSNotificationCenter defaultCenter] postNotificationName:@"showPosts" object:fetchedObjects];
}

- (void)flushData {
    NSArray *entities = self.managedObjectModel.entities;
    for (NSEntityDescription *entityDescription in entities) {
        [self deleteAllObjectsWithEntityName:entityDescription.name];
    }
    [[NSNotificationCenter defaultCenter] postNotificationName:@"showPosts" object:nil];
    [[NSNotificationCenter defaultCenter] postNotificationName:@"showComments" object:nil];
}

- (void)deleteAllObjectsWithEntityName:(NSString*)entityName {
    NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:entityName];
    fetchRequest.includesPropertyValues = NO;
    fetchRequest.includesSubentities = NO;
    NSError *error;
    NSArray *items = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
    for (NSManagedObject *managedObject in items) {
        [self.managedObjectContext deleteObject:managedObject];
    }
}

- (void)getCommentsForPostId:(NSNotification *)notification {
    NSNumber *postId = [notification object];
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Post" inManagedObjectContext:[self managedObjectContext]];
    [fetchRequest setEntity:entity];
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"postId == %@", postId];
    [fetchRequest setPredicate:predicate];
    NSError *error;
    NSArray *items = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
    Post *post = [items objectAtIndex:0];
    NSArray *comments = [[post comments] allObjects];
    [[NSNotificationCenter defaultCenter] postNotificationName:@"showComments" object:comments];
}

- (BOOL)checkIfPostIdExists:(NSNumber*)postId {
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Post" inManagedObjectContext:[self managedObjectContext]];
    [fetchRequest setEntity:entity];
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"postId == %@", postId];
    [fetchRequest setPredicate:predicate];
    NSError *error;
    NSArray *items = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
    return ([items count] > 0);
}

- (BOOL)checkIfCommentIdExists:(NSNumber*)commentId {
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"PostComment" inManagedObjectContext:[self managedObjectContext]];
    [fetchRequest setEntity:entity];
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"comId == %@", commentId];
    [fetchRequest setPredicate:predicate];
    NSError *error;
    NSArray *items = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
    return ([items count] > 0);
}

#pragma mark - Core Data stack

.....

In applicationDidFinishLaunching we define three observers: The first one should be triggered, if the user clicks on the flushData button in the view, it calls the method “flushData”. The second one is triggered when the user clicks the “sync” button, it calls the method “syncData”. The last observer gets the comments for a certain post, so the ViewController gets the data for the comment table. The call “showPostsInView” starts the application and displays the available posts per method call.

The method “receivePostsFromBlog” gets the posts from the defined URL. I iterates over the posts in the JSON answer. For every id of a post it checks, if there is already such an id in the coredata database. If such an id exists, it skips adding of the posts and calls the next method (which checks comments for a post) directly. If not, it adds the posts to the coredata database and consecutively calls the method which checks for comments.

“receiveCommentsFromBlogForPost” works the same way as “receivePostsFromBlog”. I receives comments for a certain posts, iterates over the results and adds the comment to the coredata database if it does not already exist.

“showPostsInView” queries the coredata database and receives all posts. After, it posts a notification called “showPosts” containing the posts as an object.

“flushData” and “deleteAllObjectsWithEntityName” are both used to flush the coredata database. One call to “flushData” is sufficient. “getCommentsForPostId” queries the coredata database for comments, which are assigned to a post. After, it posts a notification called “showComments”, containing the comments as an object.

“checkIfPostIdExists” and “checkIfCommentIdExists” are both helper methods for the first two methods. They check, if a post/comment with a certain ID exists in the coredata database and return NO/YES accordingly.

Changing the storyboard and the ViewController

Using the storyboard, we will add two buttons under the table views. Add two “Push Buttons” and drag them onto the storyboard:

Add buttons to the view (coredata part)

Add buttons to the view

In ViewController.h, add the following two properties:

@property (nonatomic, strong) IBOutlet NSButton *deleteData;
@property (nonatomic, strong) IBOutlet NSButton *syncButton;

Connect these properties with the according button of the storyboard using the drag and drop feature of Xcode:

Connecting the properties with the buttons of the storyboard (coredata tutorial part)

Connecting the properties with the buttons of the storyboard

The implementation of ViewController.m looks like this:

#import "ViewController.h"
#import "Post.h"
#import "PostComment.h"

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    // Do any additional setup after loading the view.
    [self.tablePosts setDelegate:self];
    [self.tablePosts setDataSource:self];
    [self.tableComments setDelegate:self];
    [self.tableComments setDataSource:self];

    [self.deleteData setTarget:self];
    [self.deleteData setAction:@selector(buttonDeleteDataClick)];
    [self.syncButton setTarget:self];
    [self.syncButton setAction:@selector(syncButtonClick)];

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(setPostsToDisplay:) name:@"showPosts" object:nil];

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(setCommentsToDisplay:) name:@"showComments" object:nil];
}

- (void)setRepresentedObject:(id)representedObject {
    [super setRepresentedObject:representedObject];

    // Update the view, if already loaded.
}

- (void)setPostsToDisplay:(NSNotification*)notification {
    self.displayPosts = [notification object];
    [self.tablePosts reloadData];
}

- (void)setCommentsToDisplay:(NSNotification*)notification {
    self.displayComments = [notification object];
    [self.tableComments reloadData];
}

- (NSView*)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {

    NSTableCellView *cellView = [tableView makeViewWithIdentifier:tableColumn.identifier owner:self];

    if ([tableView.identifier isEqualToString:@"TablePosts"]) {
        Post *post = [self.displayPosts objectAtIndex:row];
        if ([tableColumn.identifier isEqualToString:@"ColumnPostId"]) {
            cellView.textField.integerValue = [post.postId integerValue];
        }
        else if ([tableColumn.identifier isEqualToString:@"ColumnPostTitle"]) {
            cellView.textField.stringValue = post.title;
        }
    } else if ([tableView.identifier isEqualToString:@"TableComments"]) {
        PostComment *pcom = [self.displayComments objectAtIndex:row];
        if ([tableColumn.identifier isEqualToString:@"ColumnCommentId"]) {
            cellView.textField.integerValue = [pcom.comId integerValue];
        } else if ([tableColumn.identifier isEqualToString:@"ColumnCommentText"]) {
            cellView.textField.stringValue = pcom.text;
        }
    }
    return cellView;
}

- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
    if ([tableView.identifier isEqualToString:@"TablePosts"]) {
        return [self.displayPosts count];
    } else if ([tableView.identifier isEqualToString:@"TableComments"]) {
        return [self.displayComments count];
    }
    return 0;
}

- (void)tableViewSelectionDidChange:(NSNotification *)notification {
    if ([[notification.object identifier] isEqualToString:@"TablePosts"]) {
        NSInteger row = [notification.object selectedRow];
        NSTextField *tf = [[[notification.object viewAtColumn:0 row:row makeIfNecessary:NO] subviews] lastObject];
        NSNumber *postId = [NSNumber numberWithInt:(int)[tf integerValue]];
        [[NSNotificationCenter defaultCenter] postNotificationName:@"getComments" object:postId];
    }
}

- (void)buttonDeleteDataClick {
    [[NSNotificationCenter defaultCenter] postNotificationName:@"flushData" object:nil];
}

- (void)syncButtonClick {
    [[NSNotificationCenter defaultCenter] postNotificationName:@"syncData" object:nil];
}

@end

We added “setDelegate” and “setAction” for the two buttons. Furthermore, there are methods which are handling the clicks on these buttons “buttonDeleteDataClick” and “syncButtonClick”. Both post notifications which will trigger the according action. Another change (compared to the previous step of the tutorial) is in the method “viewForTableColumn”. Since we generated the model classes via coredata, postId is not an NSInteger but a NSNUmber. This means, we should access the id via method “[post.postId integerValue]” rather than “post.postId”.

After running the application, you should be able to sync and delete data. If new posts or comments are available on the blog, one click on sync will get that data! From now you could implement further logic to receive more information on posts and comments, or to update changed contents, or to even regard changed contents on update procedures.

The whole content is available at GitHub, click here.

Other parts of the tutorial:

First step: Implemented the basic application logic, posts and comments exist only as mock-data. Click here to view the post.

Second step: Connecting the application to a wordpress blog so it used the service. Click here to view the post.

stackoverflow plugin for wordpress (stackoverflow+)

Recently I wanted to display my stackoverflow account details on my blog – I found the plugin stackoverflow+ which I liked from the screenshots. After installing, it didn’t work. The widget itself displayed, but all the fields it should receive from stackoverflow stayed empty. After viewing the sources I noticed that my hosting didn’t provide zlib, so I implemented and alternative implementation to use curl, in case zlib is not available. The file wsp-functions.php will now look like this:

<?php

function sanitizeObject($d)
{
    if (is_object($d)) {
        $d = get_object_vars($d);
    }
    if (is_array($d)) {
        return array_map(__FUNCTION__, $d);
    } else {
        return $d;
    }
}

/**
 * Uses the Stackoverflow API to get certain data
 *
 * @param string $ids
 * @param string $type
 * @return array|mixed
 */
function getUserData($ids, $type = '')
{
    // Define Local Variables
    $site = 'stackoverflow';
    $baseurl = 'http://api.stackexchange.com/2.2/users/';
    $apikey = 'sbI47iM9fyMqLqD0sA2T8A((';

    //Construct Url and Get Data off SO and use Sanitizer.
    if ($type == '') {
        $url = $baseurl . $ids . '?key=' . $apikey . '&site=' . $site . '&order=desc&sort=reputation&filter=default';
    } else if ($type == 'byQuestionids') {
        $url = 'http://api.stackexchange.com/2.2/questions/' . $ids . '?key=' . $apikey . '&site=' . $site . '&order=desc&sort=activity';
    } else {
        $url = $baseurl . $ids . '/' . $type . '?key=' . $apikey . '&site=' . $site . '&order=desc&sort=activity&filter=default';
    }
    if (function_exists('curl_version') === true)
        $results = json_decode(getJsonFromUrl_curl($url));
    else
        $results = json_decode(getJsonFromUrl_zlib($url));
    $results = sanitizeObject($results);
    return $results;
}

/**
 * Gets the json data from the given URL (curl implementation)
 *
 * @param string $url URL as string
 * @return string returns the json data as string
 */
function getJsonFromUrl_curl($url) {
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_ENCODING, 'gzip');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    $data = curl_exec($ch);
    curl_close($ch);
    return $data;
}

/**
 * Gets the json data from the given URL (zlib implementation)
 *
 * @param string $url URL as string
 * @return string returns the json data as string
 */
function getJsonFromUrl_zlib($url) {
    $context = array('http'=>array('header'=>"Accept-Encoding: gzip\r\n"));
    $url = "compress.zlib://" . $url;
    return file_get_contents($url, false, $context);
}

?>

What happens: In line 37 we do a check to see, if curl is available on the server. If yes, the function “getJsonFromUrl_curl($url)” is called, so the stackexchange API  is consumed using curl. If curl is not available, it will use “getJsonFromUrl_zlib($url)”, doing the same using zlib. This procedure requires either curl or zlib to be installed.

In the file “stackoverflow-plus.php” I implemented a check for zlib and curl on plugin activation. If neither is available, plugin activation will fail with a message to the user “Either zlib or curl needs to be installed on the server!”.

<?php
/*
Plugin Name: Stackoverflow+
Plugin URI: http://techstricks.com/wp-plugins/
Description: Stackoverflow Plus integreates your <a href="http://www.stackoverflow.com">Stackoverflow</a> profile with your word press website. Show your Stackoverflow profile, Questions, Answers, Reputation, Badges and much more through a easy to configure widget.
Version: 1.0
Author: Amyth Arora
Author URI: http://www.techstricks.com
*/

load_plugin_textdomain('wordpress-stackoverflow-plus');

if (!defined('WSP_VERSION')) {
    define('WSP_VERSION','1.0');
}
if (!defined('WSP_AUTHOR')) {
    define('WSP_AUTHOR','Amyth Arora');
}
// import the Widget Class
require_once( plugin_dir_path( __FILE__ ) . 'wsp-widget.php');

//Add the Stylesheet
add_action('wp_enqueue_scripts', 'add_wsp_style');
function add_wsp_style(){
    wp_register_style('wsp-style', plugins_url('css/stackoverflow-plus.css', __FILE__));
    wp_enqueue_style('wsp-style');
}

function stackoverflowplus_activate() {
    if (function_exists('curl_version') === false && function_exists('gzcompress') === false ) {
        echo 'Either zlib or curl needs to be installed on the server!';
        die;
    }
}
register_activation_hook( __FILE__, 'stackoverflowplus_activate' );
?>

I also turned off the accept rate from the widget settings, it doesn’t seem to be provided anymore, see this link. Please note that I only did the modifications for curl – the plugin itself was written by @mytharora.