Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Mobile / iOS

How To: Use Leaderboards on iOS (iPhone/iPad) with XPG

0.00/5 (No votes)
26 Jan 2012CPOL3 min read 18K  
Use leaderboards on iOS (iPhone/iPad) with XPG

This is a continuation of the How To series. This first post is here.

This article will cover:

  • Leaderboard types and setup
  • Accessing leaderboards in game
  • Posting a score to a leaderboard
  • Retrieving scores from a leaderboard

Prerequisites

Leaderboard Types and Setup

In order to setup a leaderboard, you’ll need to have a free developer account with XPG and have registered a game in the management app. Assuming you’ve done these things, you can click on the leaderboards tab in the management app and begin defining your leaderboards.

image

In the above image, you can see the leaderboard dialog. As with most display values in the XPG system, the leaderboard name is globalized to help you provide a local experience for the end user. A display image can be defined by clicking the icon in the top right corner. The XPG system allows you to define a game-unique identifier for your leaderboard which is particularly helpful when migrating from another system to XPG, or implementing XPG along side another system. The available score types are Float, Integer, Money, and Time and each can be used with a score order of Ascending or Descending. The score order indicates if the high scores take high ranks (Descending) or if the lower scores take high ranks (Ascending). You can also limit this leaderboard’s availability by game version. Game versions are defined on the Access Keys tab in the Games area of the management app. Each version gets a new access key (or secret key) which positively identifies your game and game version.

image

Accessing Leaderboards in Game

As with many things in XPG, accessing leaderboards within your game is very simple. When your game was initialized (discussed in this post), the leaderboards defined in the XPG Management App were loaded in-game. Once these leaderboards were loaded, the first page of scores for each of them was also retrieved and loaded. All the objects provided in the XPG API for MonoTouch implement the INotifyPropertyChanged interface to provide UI binding assistance and to help simplify your coding effort.

Objective-C code
Objective-C
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";    
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault 
                       reuseIdentifier:CellIdentifier] autorelease];
    }
    
    XPGLeaderboardScoreInfo* score = [((XPGLeaderboardInfo*)
    [[XPGAPI getCurrentGame].Leaderboards objectAtIndex:boardIndex]).Scores objectAtIndex:indexPath.row];
    NSNumberFormatter* formatter = [[NSNumberFormatter new] autorelease];
    formatter.numberStyle = NSNumberFormatterDecimalStyle;
        
    UILabel* scoreValLabel = [[UILabel alloc] initWithFrame:CGRectMake(350, 7, 150, 36)];
    scoreValLabel.text = [formatter stringFromNumber:[NSNumber numberWithDouble:score.UserScore]];
    scoreValLabel.textAlignment = UITextAlignmentRight;
        
    cell.textLabel.text = [NSString stringWithFormat:@"%d. %@", 
              score.UserRank, score.UserNameDisplay, nil];
    cell.accessoryView = scoreValLabel;
    
    return cell;
}
C# code
Objective-C
public override UITableViewCell GetCell
(UITableView tableView, MonoTouch.Foundation.NSIndexPath indexPath)
{
    string cellIdentifier = string.Format("CellForSection{0}", indexPath.Section);
    var cell = tableView.DequeueReusableCell(cellIdentifier);
    if(cell == null)
    {
        cell = new UITableViewCell(UITableViewCellStyle.Default, cellIdentifier);
    }

    if(indexPath.Section == 0)
    {
        XPGLeaderboardScoreInfo score = 
        XPGAPI.Instance.CurrentGame.Leaderboards[controller.BoardIndex].Scores[indexPath.Row];

        UILabel scoreValLabel = new UILabel(new RectangleF(350f, 7f, 150f, 36f));
        scoreValLabel.Text = score.UserScore.ToString();
        scoreValLabel.TextAlignment = UITextAlignment.Right;
        
        cell.TextLabel.Text = 
        string.Format("{0}. {1}", score.UserRank, score.UserNameDisplay);
        cell.AccessoryView = scoreValLabel;
    }
    else if(indexPath.Section == 1)
    {
        switch(indexPath.Row)
        {
            case 0:                
                cell.TextLabel.Text = "Post High Score";
                break;
            case 1:                
                cell.TextLabel.Text = "Post Medium Score";
                break;
            case 2:                
                cell.TextLabel.Text = "Post Low Score";
                break;
            case 3:                
                cell.TextLabel.Text = "Refresh Scores";
                break;                
            default:
                break;
        }
    }

    return cell;
}

Posting a Score to a Leaderboard

There are overloads in C# and additional methods in Objective-C provided to work with each of the score types mentioned above. The API will perform local updates of the leaderboard data after posting a score to the server when it detects that no user data from other players has affected the ranking other than the local user. Alternatively, the API will also initiate a full update of the leaderboard local data if it detects that one or more users have affected the leaderboard ranking other than the local user. This is all handled auto-magically in order to conserve bandwidth, processor time, and device battery.

Here is an example of score posting simplicity.

Objective-C code
Objective-C
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (indexPath.section == 1)
    {
        XPGLeaderboardInfo* leaderboard = 
        [[XPGAPI getCurrentGame].Leaderboards objectAtIndex:boardIndex];
        if (leaderboard != nil)
        {
            NSArray* sortedArray;
            NSArray* sortDescriptors;
            NSSortDescriptor* sortDescriptor;
            double scoreLow, scoreHigh;
            
            sortDescriptor = [[[NSSortDescriptor alloc] 
                    initWithKey:@"UserScore" ascending:NO] autorelease];
            sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
            sortedArray = [leaderboard.Scores sortedArrayUsingDescriptors:sortDescriptors];
            
            scoreHigh = ((XPGLeaderboardScoreInfo*)[sortedArray objectAtIndex:0]).UserScore;
            
            sortDescriptor = [[[NSSortDescriptor alloc] 
                    initWithKey:@"UserScore" ascending:YES] autorelease];
            sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
            sortedArray = [leaderboard.Scores sortedArrayUsingDescriptors:sortDescriptors];
            
            scoreLow = ((XPGLeaderboardScoreInfo*)[sortedArray objectAtIndex:0]).UserScore;
            
            switch (indexPath.row)
            {
                case 0:
                    [XPGAPI submitScoreFor:leaderboard.LeaderboardTag withLong:
                    (scoreHigh + 10) andCallback:self];
                    break;
                case 1:
                    [XPGAPI submitScoreFor:leaderboard.LeaderboardTag withLong:(
                             scoreLow + (scoreHigh - scoreLow)) andCallback:self];
                    break;
                case 2:
                    [XPGAPI submitScoreFor:leaderboard.LeaderboardTag withLong:
                    (scoreHigh - 10) andCallback:self];
                    break;            
                default:
                    break;
            }
        }
    }
    
    [tableView deselectRowAtIndexPath:indexPath animated:true];
}
C# code
public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
{
    if(indexPath.Section == 1)
    {
        double lowScore, highScore;
        XPGLeaderboardInfo leaderboard = 
        XPGAPI.Instance.CurrentGame.Leaderboards[controller.BoardIndex];
        if(leaderboard != null)
        {
            lowScore = leaderboard.Scores.Min(x => x.UserScore);
            highScore = leaderboard.Scores.Max(x => x.UserScore);

            switch(indexPath.Row)
            {
                case 0:
                    XPGAPI.Instance.SubmitScore
                    (leaderboard.LeaderboardTag, (highScore + 10), HandleScoreSubmitted);
                    break;
                case 1:
                    XPGAPI.Instance.SubmitScore(leaderboard.LeaderboardTag, 
                               (lowScore + (highScore - lowScore)), HandleScoreSubmitted);
                    break;
                case 2:
                    XPGAPI.Instance.SubmitScore
                    (leaderboard.LeaderboardTag, (highScore - 10), HandleScoreSubmitted);
                    break;                
                default:
                    break;
            }
        }
    }

    tableView.DeselectRow(indexPath, true);
}

Retrieving Scores from a Leaderboard

Retrieving scores from XPG is equally simple. The scores are stored locally on the leaderboard instance of the initialized game instance, so there are no collections for you to keep up with and manage on your own. Also, leaderboards support paging, so you are not required to have all the ranked user scores in memory at once.

Objective-C code
Objective-C
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (indexPath.section == 1)
    {
        XPGLeaderboardInfo* leaderboard = 
        [[XPGAPI getCurrentGame].Leaderboards objectAtIndex:boardIndex];
        if (leaderboard != nil)
        {
            switch (indexPath.row)
            {
                case 3:
                    [XPGAPI getScoresFor:leaderboard.LeaderboardTag atOffset:0 withCallback:self];
                    break;            
                default:
                    break;
            }
        }
    }
    
    [tableView deselectRowAtIndexPath:indexPath animated:true];
}
C# code
C#
public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
{
    if(indexPath.Section == 1)
    {
        XPGLeaderboardInfo leaderboard = 
        XPGAPI.Instance.CurrentGame.Leaderboards[controller.BoardIndex];
        if(leaderboard != null)
        {
            switch(indexPath.Row)
            {
                case 3:
                    XPGAPI.Instance.GetScores
                    (leaderboard.LeaderboardTag, 0, HandleScoresObtained);
                    break;                
                default:
                    break;
            }
        }
    }

    tableView.DeselectRow(indexPath, true);
}

The Full Code

Objective-C code
Objective-C
#import <UIKit/UIKit.h>
#import <XPGLive/XPGLive.h>

@interface LeaderboardViewController : UITableViewController  <XPGAPIReceiver>
{
    int boardIndex;
}

@property (nonatomic) int boardIndex;

-(void)reset;

@end
#import "LeaderboardViewController.h"

@implementation LeaderboardViewController

@synthesize boardIndex;

- (id)initWithStyle:(UITableViewStyle)style
{
    self = [super initWithStyle:style];
    if (self) {
        // Custom initialization
    }
    return self;
}

#pragma mark - View lifecycle

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    
    XPGLeaderboardInfo* leaderboard = (XPGLeaderboardInfo*)
       [[XPGAPI getCurrentGame].Leaderboards objectAtIndex:boardIndex];
    self.title = leaderboard.DisplayName;
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    // Return YES for supported orientations
    return (interfaceOrientation == UIInterfaceOrientationPortrait);
}

#pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 2;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    NSInteger retVal = 0;
    
    if (section == 0)
    {
        XPGLeaderboardInfo* leaderboard = (XPGLeaderboardInfo*)
          [[XPGAPI getCurrentGame].Leaderboards objectAtIndex:boardIndex];
        retVal = [leaderboard.Scores count];
    }
    else if (section == 1) { retVal = 4; }
    
    return retVal;
}

- (UITableViewCell *)tableView:(UITableView *)tableView 
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";    
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] 
          initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
    }
    
    if (indexPath.section == 0)
    {
        XPGLeaderboardScoreInfo* score = 
          [((XPGLeaderboardInfo*)[[XPGAPI getCurrentGame].Leaderboards 
             objectAtIndex:boardIndex]).Scores objectAtIndex:indexPath.row];
        NSNumberFormatter* formatter = [[NSNumberFormatter new] autorelease];
        formatter.numberStyle = NSNumberFormatterDecimalStyle;
        
        UILabel* scoreValLabel = 
        [[UILabel alloc] initWithFrame:CGRectMake(350, 7, 150, 36)];
        scoreValLabel.text = 
        [formatter stringFromNumber:[NSNumber numberWithDouble:score.UserScore]];
        scoreValLabel.textAlignment = UITextAlignmentRight;
        
        cell.textLabel.text = [NSString stringWithFormat:@"%d. %@", 
                                        score.UserRank, score.UserNameDisplay, nil];
        cell.accessoryView = scoreValLabel;
    }
    else if (indexPath.section == 1)
    {
        switch (indexPath.row)
        {
            case 0:                
                cell.textLabel.text = @"Post High Score";
                break;
            case 1:                
                cell.textLabel.text = @"Post Medium Score";
                break;
            case 2:                
                cell.textLabel.text = @"Post Low Score";
                break;
            case 3:                
                cell.textLabel.text = @"Refresh Scores";
                break;                
            default:
                break;
        }
    }
    
    return cell;
}

#pragma mark - XPG API delegate

-(void)getScoresCompletedWith:(XPGAPIResponse *)response
{
    [self.tableView reloadData];
}

-(void)submitScoreCompletedWith:(XPGAPIResponse *)response
{
    [self.tableView reloadData];
}

-(void)reset
{
}

#pragma mark - Table view delegate

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (indexPath.section == 1)
    {
        XPGLeaderboardInfo* leaderboard = 
        [[XPGAPI getCurrentGame].Leaderboards objectAtIndex:boardIndex];
        if (leaderboard != nil)
        {
            NSArray* sortedArray;
            NSArray* sortDescriptors;
            NSSortDescriptor* sortDescriptor;
            double scoreLow, scoreHigh;
            
            sortDescriptor = [[[NSSortDescriptor alloc] 
            initWithKey:@"UserScore" ascending:NO] autorelease];
            sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
            sortedArray = [leaderboard.Scores sortedArrayUsingDescriptors:sortDescriptors];
            
            scoreHigh = ((XPGLeaderboardScoreInfo*)[sortedArray objectAtIndex:0]).UserScore;
            
            sortDescriptor = [[[NSSortDescriptor alloc] 
            initWithKey:@"UserScore" ascending:YES] autorelease];
            sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
            sortedArray = [leaderboard.Scores sortedArrayUsingDescriptors:sortDescriptors];
            
            scoreLow = ((XPGLeaderboardScoreInfo*)[sortedArray objectAtIndex:0]).UserScore;
            
            
            switch (indexPath.row)
            {
                case 0:
                    [XPGAPI submitScoreFor:leaderboard.LeaderboardTag 
                    withLong:(scoreHigh + 10) andCallback:self];
                    break;
                case 1:
                    [XPGAPI submitScoreFor:leaderboard.LeaderboardTag 
                    withLong:(scoreLow + (scoreHigh - scoreLow)) andCallback:self];
                    break;
                case 2:
                    [XPGAPI submitScoreFor:leaderboard.LeaderboardTag 
                    withLong:(scoreHigh - 10) andCallback:self];
                    break;
                case 3:                
                    [XPGAPI getScoresFor:leaderboard.LeaderboardTag atOffset:0 withCallback:self];
                    break;                
                default:
                    break;
            }
        }
    }
    
    [tableView deselectRowAtIndexPath:indexPath animated:true];
}

@end
C# code
C#
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using MonoTouch.Foundation;
using MonoTouch.UIKit;
using XPG;

namespace XPG.Demo.MT
{
    public partial class LeaderboardViewController : UITableViewController
    {
        public int BoardIndex { get; set; }

        //loads the LeaderboardViewController.xib file and connects it to this object
        public LeaderboardViewController() : base ("LeaderboardViewController", null)
        {
        }

        public override void ViewDidLoad()
        {
            base.ViewDidLoad();

            this.Title = XPGAPI.Instance.CurrentGame.Leaderboards[BoardIndex].DisplayName;
            this.TableView.Source = new DataSource(this);
        }
        
        public override bool ShouldAutorotateToInterfaceOrientation(
                                     UIInterfaceOrientation toInterfaceOrientation)
        {
            return toInterfaceOrientation.Equals(UIInterfaceOrientation.Portrait);
        }

        public override void DidReceiveMemoryWarning()
        {
            base.DidReceiveMemoryWarning();
        }
        
        public override void ViewDidUnload()
        {
            base.ViewDidUnload();
        }
        
        class DataSource : UITableViewSource
        {
            LeaderboardViewController controller;

            public DataSource(LeaderboardViewController controller)
            {
                this.controller = controller;
            }
            
            // Customize the number of sections in the table view.
            public override int NumberOfSections(UITableView tableView)
            {
                return 2;
            }

            public override int RowsInSection(UITableView tableview, int section)
            {
                return (section == 1 ? 4 : 
                  XPGAPI.Instance.CurrentGame.Leaderboards[controller.BoardIndex].Scores.Count);
            }

            // Customize the appearance of table view cells.
            public override UITableViewCell GetCell(UITableView tableView, 
                            MonoTouch.Foundation.NSIndexPath indexPath)
            {
                string cellIdentifier = 
                  string.Format("CellForSection{0}", indexPath.Section);
                var cell = tableView.DequeueReusableCell(cellIdentifier);
                if(cell == null)
                {
                    cell = new UITableViewCell(UITableViewCellStyle.Default, cellIdentifier);
                }

                if(indexPath.Section == 0)
                {
                    XPGLeaderboardScoreInfo score = 
                      XPGAPI.Instance.CurrentGame.Leaderboards
                      [controller.BoardIndex].Scores[indexPath.Row];

                    UILabel scoreValLabel = new UILabel(new RectangleF(350f, 7f, 150f, 36f));
                    scoreValLabel.Text = score.UserScore.ToString();
                    scoreValLabel.TextAlignment = UITextAlignment.Right;
        
                    cell.TextLabel.Text = string.Format("{0}. {1}", 
                           score.UserRank, score.UserNameDisplay);
                    cell.AccessoryView = scoreValLabel;
                }
                else if(indexPath.Section == 1)
                {
                    switch(indexPath.Row)
                    {
                        case 0:                
                            cell.TextLabel.Text = "Post High Score";
                            break;
                        case 1:                
                            cell.TextLabel.Text = "Post Medium Score";
                            break;
                        case 2:                
                            cell.TextLabel.Text = "Post Low Score";
                            break;
                        case 3:                
                            cell.TextLabel.Text = "Refresh Scores";
                            break;                
                        default:
                            break;
                    }
                }

                return cell;
            }
            
            public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
            {
                if(indexPath.Section == 1)
                {
                    double lowScore, highScore;
                    XPGLeaderboardInfo leaderboard = 
                      XPGAPI.Instance.CurrentGame.Leaderboards[controller.BoardIndex];
                    if(leaderboard != null)
                    {
                        lowScore = leaderboard.Scores.Min(x => x.UserScore);
                        highScore = leaderboard.Scores.Max(x => x.UserScore);

                        switch(indexPath.Row)
                        {
                            case 0:
                                XPGAPI.Instance.SubmitScore(leaderboard.LeaderboardTag, 
                                        (highScore + 10), HandleScoreSubmitted);
                                break;
                            case 1:
                                XPGAPI.Instance.SubmitScore(leaderboard.LeaderboardTag, 
                                         (lowScore + (highScore - lowScore)), HandleScoreSubmitted);
                                break;
                            case 2:
                                XPGAPI.Instance.SubmitScore(leaderboard.LeaderboardTag, 
                                         (highScore - 10), HandleScoreSubmitted);
                                break;
                            case 3:
                                XPGAPI.Instance.GetScores
                                         (leaderboard.LeaderboardTag, 0, HandleScoresObtained);
                                break;                
                            default:
                                break;
                        }
                    }
                }

                tableView.DeselectRow(indexPath, true);
            }

            private void HandleScoreSubmitted(XPGAPIResponse<XPGScoreResultInfo> response)
            {
                controller.TableView.ReloadData();
            }

            private void HandleScoresObtained(XPGAPIResponse<XPGScoresResultInfo> response)
            {
                controller.TableView.ReloadData();
            }
        }
    }
}

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)