Update 10/11/09: Fixed code display.
It’s not complete yet, but the basics work, so here’s my effort at building a MonoTouch binding for AdMob. This is my first attempt at making a MonoTouch binding to an Objective-C type, so please make suggestions.
Follow these MonoTouch-specific instructions adapted from AdMob’s wiki page. The paragraph numbers correspond to their instructions.
(1) Copy the 3.0 libraries (.a files) found in the AdMob SDK extras directory into your MonoTouch project.
(2) Add these to your additional mtouch arguments:
-gcc_flags “-framework QuartzCore -Lpath-to-the-admob-library -lAdMobSimulator3_0 -ObjC”
(3) “Get a publisher id from www.admob.com.”
(4) “Integrate AdMob ads with your app.” I chose this technique:
(b) “Add an ad to a view programmatically.”
// Create and customize the delegate
AdMobDelegate adMobDelegate = new AdMobDelegate();
adMobDelegate.PublisherId = new NSString(myPublisherID);
adMobDelegate.AdReceived += delegate (AdMobView adView)
{
this.View.AddSubview(advertView);
Thread thread = new Thread(new ThreadStart(RequestFreshAd));
thread.Start();
};
// Example of changing the background color
adMobDelegate.AdBackgroundColor = new UIColor(0, 0, 0, 1);
// Create the AdMobView referencing the delegate you just created
AdMobView advertView =
AdMobView.RequestAdWithDelegate(adMobDelegate);
// In this example, the AdMobView is placed just above a UIToolBar
float advertHeight = 48;
advertView.Frame = new RectangleF (
0,
this.View.Frame.Height - advertHeight - toolbar.Frame.Height,
this.View.Frame.Width,
advertHeight);
Departing from AdMob’s instructions (numbers don’t correspond):
(5) Sample code for requesting a fresh ad:
private void RequestFreshAd()
{
while (true)
{
Thread.Sleep(60000); // Request fresh ad once per minute
this.InvokeOnMainThread(advertView.RequestFreshAd);
}
}
(6) The AdMobView class works except for the Version property. I don’t know why it’s sending the message to the AdMobViewInternal class and so getting an unrecognized selector.
using System;
using System.Drawing;
using MonoTouch.Foundation;
using MonoTouch.ObjCRuntime;
using MonoTouch.UIKit;
namespace AdMob
{
[Register ("AdMobView")]
public class AdMobView : UIView
{
static Selector selInit = new Selector("init");
static Selector selRequestAdWithDelegate =
new Selector("requestAdWithDelegate:");
static Selector selRequestFreshAd =
new Selector("requestFreshAd");
static Selector selVersion = new Selector("version");
[Export ("init")]
public AdMobView()
: base(NSObjectFlag.Empty)
{
Handle = Messaging.IntPtr_objc_msgSend(
this.Handle, selInit.Handle);
}
// This constructor must be present so that
// MonoTouch can create instances of this type
// from Objective-C code.
public AdMobView (IntPtr handle)
: base(handle)
{
}
/**
* Initiates an ad request and returns a view that will
* contain the results; the delegate is alerted when the
* ad is ready to display (or has failed to load); this is
* a good opportunity to attach the view to your
* hierarchy. If you already have a AdMobView with an
* ad loaded, and simply want to show a new ad in the
* same location, you may use -requestFreshAd instead
* (see below).
*
* This method should only be called from a run loop in
* default run loop mode. If you don't know what that
* means, you're probably ok. If in doubt, check whether
* ([[NSRunLoop currentRunLoop] currentMode] ==
* NSDefaultRunLoopMode).
*/
public static AdMobView RequestAdWithDelegate(
AdMobDelegate Delegate)
{
// Get class type
Class adMobViewClass = new Class("AdMobView");
return (AdMobView) Runtime.GetNSObject(
Messaging.IntPtr_objc_msgSend_IntPtr(
adMobViewClass.Handle,
selRequestAdWithDelegate.Handle,
Delegate.Handle));
}
/**
* Causes an existing AdMobView to display a fresh ad.
* If an ad successfully loads, it is animated in with a
* flip; if not, this call fails silently and the old ad
* remains onscreen.
*
* Note that, during the flip, views under the AdMobView
* will be exposed.
*
* To preserve the user experience, we recommend loading
* fresh ads no more frequently than once per minute.
*/
public virtual void RequestFreshAd()
{
Messaging.void_objc_msgSend(
this.Handle, selRequestFreshAd.Handle);
}
/**
* Returns the version of the current SDK.
*/
public virtual string Version
{
get
{
return ((NSString) Runtime.GetNSObject(
Messaging.IntPtr_objc_msgSend(
this.Handle,
selVersion.Handle))).ToString();
}
}
}
}
(7) This AdMobDelegate class is unfinished:
using System;
using System.Drawing;
using MonoTouch.Foundation;
using MonoTouch.ObjCRuntime;
using MonoTouch.UIKit;
namespace AdMob
{
[Register("AdMobDelegate")]
public class AdMobDelegate : NSObject
{
public delegate void AdReceivedDelegate (AdMobView adView);
public event AdReceivedDelegate AdReceived;
public delegate void AdReceiveFailedDelegate (AdMobView adView);
public event AdReceivedDelegate AdReceiveFailed;
private static Selector selInit = new Selector("init");
[Export ("init")]
public AdMobDelegate()
: base(NSObjectFlag.Empty)
{
Handle = Messaging.IntPtr_objc_msgSend(this.Handle, selInit.Handle);
}
// This constructor must be present so that MonoTouch
// can create instances of this type from Objective-C code.
public AdMobDelegate (IntPtr handle)
: base(handle)
{
}
// Use this to provide a publisher id for an ad request. Get a publisher id
// from http://www.admob.com
private NSString myPublisherID;
public virtual NSString PublisherId
{
[Export ("publisherId")]
get { return myPublisherID; }
set { myPublisherID = value; }
}
// Sent when an ad request loaded an ad; this is a good opportunity to add
// this view to the hierachy, if it has not yet been added.
// Note that this will only ever be sent once per AdMobView, regardless of whether
// new ads are subsequently requested in the same AdMobView.
[Export ("didReceiveAd:")]
public virtual void DidReceiveAd (AdMobView adView)
{
if (AdReceived != null)
AdReceived (adView);
}
// Sent when an ad request failed to load an ad.
// Note that this will only ever be sent once per AdMobView, regardless of whether
// new ads are subsequently requested in the same AdMobView.
[Export ("didFailToReceiveAd:")]
public virtual void DidFailToReceiveAd (AdMobView adView)
{
if (AdReceiveFailed != null)
AdReceiveFailed (adView);
}
// Specifies the ad background color, for tile+text ads.
// Defaults to [UIColor colorWithRed:0.443 green:0.514 blue:0.631 alpha:1], which is a chrome-y color.
// Note that the alpha channel in the provided color will be ignored and treated as 1.
// We recommend against using a white or very light color as the background color, but
// if you do, be sure to implement adTextColor and useGraySpinner.
// Grayscale colors won't function correctly here. Use e.g. [UIColor colorWithRed:0 green:0 blue:0 alpha:1]
// instead of [UIColor colorWithWhite:0 alpha:1] or [UIColor blackColor].
private UIColor adBackgroundColor;
public virtual UIColor AdBackgroundColor
{
[Export ("adBackgroundColor")]
get { return adBackgroundColor; }
set { adBackgroundColor = value; }
}
// Specifies the primary text color for ads.
// Defaults to [UIColor whiteColor].
private UIColor primaryTextColor;
public virtual UIColor PrimaryTextColor
{
[Export ("primaryTextColor")]
get { return primaryTextColor; }
set { primaryTextColor = value; }
}
// Specifies the secondary text color for ads.
// Defaults to [UIColor whiteColor].
private UIColor secondaryTextColor;
public virtual UIColor SecondaryTextColor
{
[Export ("secondaryTextColor")]
get { return secondaryTextColor; }
set { secondaryTextColor = value; }
}
// When a spinner is shown over the adBackgroundColor (e.g. on clicks), it is by default
// a white spinner. If this returns YES, a gray spinner will be used instead,
// which looks better when the adBackgroundColor is white or very light in color.
/* - (BOOL)useGraySpinner;
// Whether AdMob may use location information. Defaults to NO.
// We ask that you respect your users' privacy and only enable location requests
// if your app already uses location information.
// Note that even if this is set to no, you will still need to include the CoreLocation
// framework to compile your app; it will simply not get used. (It is a dynamic
// framework, so including it will not increase the size of your app.)
- (BOOL)mayAskForLocation;
// If you have location information available for the user at the time that you
// make the ad request, you may provide it here (and it is recommended that you do
// so, to improve user experience and preserve battery life). If this method is implemented,
// AdMob will not request location information from the OS, even if the method returns
// nil (which it should if the user's location is not known); if this method is not
// implemented, then AdMob will request location information directly from the OS.
//
// If mayAskForLocation (above) is not implemented, or returns NO, this will not be called
// and may be completely ignored.
- (CLLocation *)location;
// If implemented, lets you specify whether to issue a real ad request or a test
// ad request (to be used for development only, of course). Defaults to NO.
- (BOOL)useTestAd;
// If implemented, lets you specify the action type of the test ad. Defaults to @"url" (web page).
// Does nothing if useTestAd is not implemented or returns NO.
// Acceptable values are @"url", @"app", @"video", @"itunes", @"call", @"canvas".
// Normally, the adservers restricts ads appropriately (e.g. no click to call ads for iPod touches).
// However, for your testing convenience, they will return any type requested for test ads.
- (NSString *)testAdAction;
// The following functions, if implemented, provide extra information
// for the ad request. If you happen to have this information, providing it will
// help select better targeted ads and will improve monetization.
//
// Keywords and search terms should be provided as a space separated string
// like "iPhone monetization San Mateo". We strongly recommend that
// you NOT hard code keywords or search terms.
//
// Keywords are used to select better ads; search terms _restrict_ the available
// set of ads. Note, then, that providing a search string may seriously negatively
// impact your fill rate; we recommend using it only when the user is submitting a
// free-text search request and you want to _only_ display ads relevant to that search.
// In those situations, however, providing a search string can yield a significant
// monetization boost.
//
// For all of these methods, if the information is not available at the time of
// the call, you should return nil.
- (NSString *)postalCode; // user's postal code, e.g. "94401"
- (NSString *)areaCode; // user's area code, e.g. "415"
- (NSDate *)dateOfBirth; // user's date of birth
- (NSString *)gender; // user's gender (e.g. @"m" or @"f")
- (NSString *)keywords; // keywords the user has provided or that are contextually relevant, e.g. @"twitter client iPhone"
- (NSString *)searchString; // a search string the user has provided, e.g. @"Jasmine Tea House San Francisco"
// You can control the appearance of the AdMob mini-browser and the nav bars
// to fit with the rest of your app. See the documentation for UIToolbar
// and UINavigationBar for details. Defaults to standard iPhone chrome.
- (UIBarStyle)embeddedWebViewBarStyle;
- (UIColor *)embeddedWebViewTintColor;
// Sent just before presenting the user a full screen view, such as a canvas page or an embedded webview,
// in response to clicking on an ad. Use this opportunity to stop animations, time sensitive interactions, etc.
- (void)willPresentFullScreenModal;
// Sent just after dismissing a full screen view. Use this opportunity to
// restart anything you may have stopped as part of -willPresentFullScreenModal:.
- (void)didDismissFullScreenModal;
*/
}
}
September 28, 2009 at 12:43 am
MonoTouch Article – MonoTouch Binding for AdMob…
Thank you for submitting this entry – Trackback from MonoTouch.Info…
October 10, 2009 at 5:43 pm
Hey -
Been trying to leave a comment, but I think WordPress is freaked out about the code. The C# prolly looks a little like javascript.
Anyway, without the code sample (which I’ll still try to post), I think the problem with the Version binding is that it’s a class (static) method of the AdMob Objective-C class.
Let’s see if WP will let me post at least this one little snippet – from your Version property (I’m removing the semicolon in case that’s part of what WP looks for):
Messaging.IntPtr_objc_msgSend(this.Handle, selVersion.Handle)))
In that line, the first argument is the Handle to your instance of your AdMob wrapper/binding thingy.
I’m pretty sure it needs to be a Handle to the *classs*.
I haven’t tried this yet, and I have no idea if it’ll work, but with MonoTouch’s Objective-C runtime namespace imported, you should be able to do this (leaving semicolons out again):
Class adMobClass = new Class(this)
Then, instead of passing “this.Handle” to the call, pass “adMobClass.Handle” – it *should* work. If not, I think it’s on the right path.
Hope this helps.
And I also hope this comment makes it through…
October 10, 2009 at 6:02 pm
Something else that *might* be a problem:
return ((NSString) Runtime.GetNSObject(
Messaging.IntPtr_objc_msgSend(this.Handle, selVersion.Handle))).ToString()
I’m a MonoTouch noob (like everybody else), so I could be way off here, but I think you’d want to create the NSString using its IntPtr constructor. So (I’m splitting this into a couple lines for clarity):
IntPtr ip = Messaging.IntPtr_objc_msgSend(adMobClass.Handle, selVersion.Handle)
Once you have your IntPtr (the handle), you could do a couple things.
One way is to create a new instance of NSString and return it like so (still splitting across lines for clarity):
NSString s = new NSString(ip)
return s.ToString()
Another is to use a static method that returns a System.String, so you don’t even have to call ToString():
return NSString.FromHandle(ip)
Either works. It’s a matter of preference, I guess. If you want an NSString back, the instance method is dandy. If you want a .Net string back, NSString.FromHandle is a shortcut.
Either way, you get to avoid some typing by not having to include Runtime.GetNSObject and then casting and so on…
Again, I hope this helps
October 12, 2009 at 1:58 am
[...] MonoTouch Binding for AdMob (discussing binding to Objective-C from C#) [...]
October 12, 2009 at 8:13 am
This comment tests inserting code in a comment.
List<string> list = new List<string>() { "Tangerine", "Mango", "Grapefruit", "Orange", "Banana" };This can be done, apparently requiring two steps:
1) Enter and post a comment with no code. Don’t post code in this little comment entry field. WordPress.com will strip out parts of your code and otherwise complain at you.
2) Press the “edit” link in your posted comment to bring up the full WordPress.com text editor. Surround your code with these tags as described on the help page: http://en.support.wordpress.com/code/
Please let me know if this doesn’t work or if you can’t edit a comment after posting it.
October 24, 2009 at 5:54 pm
Is it possible for someone to publish a working version of this? I have been trying to implement this but am at a dead end. I have a complicated news reader using a tab controller with navigation controllers within the tabs so that I can navigate to stories. I have been unable to implement even a simple applciation with this ad controller :S. Anyone?
Chad
October 24, 2009 at 7:17 pm
See http://www.sabonrai.com/downloads/MT_SampleTableView2.zip for a sample project. It doesn’t have a navigation controller, but it will work if you substitute your publisher ID as indicated in the variable myPublisherID.
By the way, the version in this sample project is as published above. I haven’t fixed it based on Rory’s comments yet.
October 25, 2009 at 12:49 am
Thanks twestley. I appreciate the sample. I can get the sample to display an ad. however, when I implement the class on my navigation controller I get this error:
AdERROR:MonoTouch.Foundation.MonoTouchException: Objective-C exception thrown. Name: NSInvalidArgumentException Reason: *** +[AdMobView requestAdWithDelegate:]: unrecognized selector sent to class 0×105f7f0
at (wrapper managed-to-native) MonoTouch.ObjCRuntime.Messaging:IntPtr_objc_msgSend_IntPtr (intptr,intptr,intptr)
at AdMob.AdMobView.RequestAdWithDelegate (AdMob.AdMobDelegate Delegate) [0x0000b] in /Users/chadrosenbohm/Downloads/Gazette NewsReader/RSSReader/AdMobView.cs:49
at RSSReader.NavController..ctor (MonoTouch.UIKit.UIViewController viewController, System.String appTitle) [0x000cb] in /Users/chadrosenbohm/Downloads/Gazette NewsReader/RSSReader/ViewControllers/NavController.xib.cs:102
Any suggestions?
October 25, 2009 at 3:56 pm
Hmm, the unrecognized selector error usually means that the interface is wrong, i.e., that the selector name is wrong here:
static Selector selRequestAdWithDelegate = new Selector("requestAdWithDelegate:");However, I don’t see why it would work in one project and not in another as long as the inteface classes compile and successfully link.
October 25, 2009 at 4:42 pm
I had to change the RequestAdWithDelegate interface to use selInit.Hand instead of selRequestAdWithDelegate.Handle. I’m not sure that worked. However, it next threw an error on the advertView.Frame =xxx. I tried setting it in the delegate.AdReceived… but still no success.
2009-10-25 16:39:04.570 RSSReader[20393:20b] *** +[AdMobView frame]: unrecognized selector sent to class 0×105f7f0
MonoTouch.Foundation.MonoTouchException: Objective-C exception thrown. Name: NSInvalidArgumentException Reason: *** +[AdMobView frame]: unrecognized selector sent to class 0×105f7f0
at (wrapper managed-to-native) MonoTouch.ObjCRuntime.Messaging:void_objc_msgSendSuper_RectangleF (intptr,intptr,System.Drawing.RectangleF)
I’ll edit and add the file implementation.
October 25, 2009 at 4:51 pm
using System;
using System.Collections.Generic;
using System.Linq;
using MonoTouch.Foundation;
using MonoTouch.UIKit;
using System.Threading;
using System.Drawing;
using AdMob;
namespace RSSReader
{
public partial class NavController : UINavigationController
{
#region Constructors
// The IntPtr and NSCoder constructors are required for controllers that need
// to be able to be created from a xib rather than from managed code
public NavController (IntPtr handle) : base(handle)
{
Initialize ();
}
[Export("initWithCoder:")]
public NavController (NSCoder coder) : base(coder)
{
Initialize ();
}
public NavController ()
{
Initialize ();
}
public NavController (UIViewController viewController, string appTitle)
{
Initialize ();
viewController.NavigationItem.RightBarButtonItem = new UIBarButtonItem ( UIBarButtonSystemItem.Refresh );
viewController.NavigationItem.RightBarButtonItem.Tag = ((FeedsController)viewController).Tag;
viewController.NavigationItem.RightBarButtonItem.Clicked += delegate(object sender, EventArgs e) {
{
if (this.ViewControllers.Length>0)
((FeedsController)this.ViewControllers[0]).ReloadRSS();
}
};
UIImageView(UIImage.FromFile(“images/logo.png”));
this.ViewControllers = new UIViewController[]{ viewController };
try
{
adMobDelegate = new AdMobDelegate();
adMobDelegate.PublisherId = new NSString(myPublisherID);
adMobDelegate.AdReceived += delegate (AdMobView adView)
{
/*
// put the ad at the bottom of the screen
advertView.Frame = new RectangleF (
0,
this.View.Frame.Height – advertHeight,
this.View.Frame.Width,
advertHeight);
*/
//viewController.View.AddSubview(advertView);
this.View.AddSubview(advertView);
Thread thread = new Thread(new ThreadStart(RequestFreshAd));
thread.Start();
};
adMobDelegate.AdBackgroundColor = new UIColor(0, 0, 0, 1);
advertView = AdMobView.RequestAdWithDelegate(adMobDelegate);
// put the ad at the bottom of the screen
advertView.Frame = new RectangleF (
0,
this.View.Frame.Height – advertHeight,
this.View.Frame.Width,
advertHeight);
}
catch (Exception ex)
{
Console.WriteLine(“AdERROR:”+ex.ToString());
}
}
private float advertHeight = 48;
private AdMobView advertView;
private AdMobDelegate adMobDelegate;
private const string myPublisherID = “MyPubID”;
private void RequestFreshAd()
{
while (true)
{
Thread.Sleep(60000); // 1 minute
this.InvokeOnMainThread(advertView.RequestFreshAd);
}
}
void Initialize ()
{
}
#endregion
}
}