Monthly Archives: August 2010

AutoMapper and MonoTouch

Sadly, it doesn’t seem to work. v1.1 of Automapper breaks the compiler completely, and v1.0 just crashes. Something about “cannot cast this object to that object” … Too bad. That would have been pretty slick.

If anyone else has done this with success, please leave a comment!

UIScrollView deliciousness… keeping the keyboard out of the way…

So I’ve got a really tall form for users to enter their profile information. The app works by swapping out views into a single content area (UIScrollView). Now, the hard part is keeping the keyboard from covering up whatever a user’s trying to enter. Enter some sugar:

On the form view controller:
(somewhere I’m calling myTextField.EditingDidBegin += HandleTfEditingDidBegin;)
void HandleTfEditingDidBegin (object sender, EventArgs e)
{
 var tf = sender as UITextField;
 if (tf != null) {
  var loc = tf.Frame.Location; // this is the textfield's placement in the view.
  if (loc.Y > 210) { // 210 happens to be the optimal LOWEST point in my case. Adjust it and see.
  if (this.NeedsScroll != null)
   this.NeedsScroll (null, new ScrollEventArgs (loc.Y - 210f)); //take the 210 back out.
  }
 }
}

And the view that manages the ScrollView:

void HandleAccountVCNeedsScroll (object sender, PhoneManageAccountViewController.ScrollEventArgs e)
{
 var offset = this.ContentAreaView.ContentOffset;
 if (e.ScrollY > offset.Y) { // only scroll down! whee!
   this.ContentAreaView.SetContentOffset (new PointF (offset.X, e.ScrollY), true);
  }
}

Oh, and finally - when I add a new view to the main UIScrollView, I use this handy method:

private void DisplayContent (UIView newView)
{
 this.ContentAreaView.AddSubview (newView);
 this.ContentAreaView.ContentSize = newView.Bounds.Size;
}

Emulating backgroundTap in Monotouch to hide keyboard

I searched and searched, and couldn’t figure out how to hide the keyboard when a user clicks on the view…

Finally, I dun figured it out on my own. It probably should be obvious to most, but here it is anyhow (place this in the code for your view controller):

public override void TouchesEnded (NSSet touches, UIEvent evt)
{
	foreach (var item in this) {
		var tf = item as UITextField;
		if (tf != null && tf.IsFirstResponder) {
			tf.ResignFirstResponder ();
		}
	}
	base.TouchesEnded (touches, evt);
}

Unwire your events to make Garbage Collector happy.

In Monotouch, I have an application that was eating up a lot of memory. When digging into Instruments, I found that it was multiple copies of images being retained well past their needed lifetime.

Generally, I love me some anonymous delegates when invoking simple actions. That is,

someButton.TouchUpInside += delegate { DoSomething(); }

However, if you are swapping views often, this becomes problematic as the UIButton will not release the image from memory, even if you manually call .Dispose() on the button and its imageview and it’s imageview’s image.

So, to prevent that behavior, I moved to using explicit methods for the delegates. This way, I can manually unhook them from the object in the .Dispose() or my personal .Cleanup() methods. This allows the button to be completely disposed and GC to work as expected. Without the underlined snippet below, I was seeing memory usage explode – to the tune of 100s of images being loaded (even though there are only ~16 images in total, and never more than 4 displayed at once).
Sample:


public UIImage image;
public override void ViewDidLoad ()
{
	this.button.SetImage(image,UIControlState.Normal);
	this.button.TouchUpInside += HandleButtonhandleTouchUpInside;
}
int Z;
void HandleButtonhandleTouchUpInside (object sender, EventArgs e)
{
	var x = 13;
	var y = 12;
	var z = x + y;
	Z = z;
}
public void Cleanup()
{
	this.button.TouchUpInside -= HandleButtonhandleTouchUpInside;
	this.button.Dispose();
	base.Dispose();
}

Aborting an NSTimer in MonoTouch

It’s really easy: timer.Invalidate();
Not being accustomed to timers, I was looking for an .Abort() method. But, it seems that Invalidate encompasses the same function.

Of course, that means you have to keep a reference around. This is in reference to my previous post about checking the fire date of a timer – It wasn’t working quite perfectly for me, so I ended up invalidating it, then initializing a new timer instance to fire the event in the future. And now, somehow, I managed to make it work.

In related news: Having too many UIView animations can cause some interesting things to happen – views showing or hiding when they’re not supposed to, random “ghost” images of your view… etc etc. Be sure to clean up and keep track of everything in the timeline when using UIView Animations.

Final Code:

private NSTimer timer;
void DoStuff()
{
	if (timer != null &&  timer.IsValid)
	{
		timer.Invalidate();
	}
	timer = NSTimer.CreateScheduledTimer(4,ReallyDoStuff);
}

UIScrollView in MonoTouch: The easy way.

Background: I’ve got an “instructions” view xib that I created in Interface Builder (because I *like* IB!). To set it up for multiple pages, I figured I’d just make it 3x the normal width: 960 px.

The Problem: Easy way to scroll, page controller, and snapping to position.

Anyhow, I found the easiest way is to do it like so:

public override void ViewDidLoad ()
{
	this.contentArea.ContentSize = new SizeF (960, 377); // set the initial content size.
	this.contentArea.AddSubview (instructionsView);
	this.pageControl.ValueChanged += HandlePageControlhandleValueChanged;
	this.contentArea.DraggingEnded += HandleContentAreahandleDraggingEnded;
	this.contentArea.DecelerationEnded += HandleContentAreahandleDecelerationEnded;
}

void HandleContentAreahandleDecelerationEnded (object sender, EventArgs e)
{
	SnapToView();
}

private void SnapToView ()
{
	int pageCount = 0;
	float offset = contentArea.ContentOffset.X;
	if (offset <= 160) {
		pageCount = 0;
	} else if (offset > 160 && offset <= 480) {
		pageCount = 1;
	} else if (offset > 480) {
		pageCount = 2;
	}
	pageControl.CurrentPage = pageCount;
	contentArea.ScrollRectToVisible (new RectangleF (320 * pageCount, 0, 320, 377), true);
}

void HandleContentAreahandleDraggingEnded (object sender, DraggingEventArgs e)
{
	SnapToView ();
}
void HandlePageControlhandleValueChanged (object sender, EventArgs e)
{
	contentArea.ScrollRectToVisible (new RectangleF (320 * pageControl.CurrentPage, 0, 320, 377), true);
}

Voila! Snaps to position when user scrolls (with or without deceleration) and also scrolls cleanly when using the scrollview with a subview larger than the displayable screen real estate.

Multiplayer in MonoTouch

The easy way is to use GameKit, good starting article at :

http://mikebluestein.wordpress.com/2010/06/14/using-gamekit-with-monotouch-2/

Though what I *really* want to do is create an ad-hoc wifi network… So far it seems that apple has restricted devs to only access existing wifi networks (IE you aren’t allowed to create your own ad-hoc network on the iphone). I’m assuming this is a “feature” designed to make it more difficult to find a way to tether your iPhone to another device.

Oh well. Buzzwordingo should have chat capabilities soon enough.

Communication is key

And simplicity is king. If you have a vestigial value in your XML/Web Service/Whatever, you can almost be certain that someone, somewhere will use it wrongly. Especially if they need to use one of those two values to submit to another one of your services.

With that in mind, remember to never, ever send any additional information unless absolutely necessary (and specifically requested with a whole set of use-cases).

MonoDroid

Oooh, I just got my MonoDroid invite. I know you’re jealous!

What AutoMapper needs.

private TDestination AutoMap<TSource, TDestination>(TSource source)
{
     if (Mapper.FindTypeMapFor(typeof(TSource), typeof(TDestination)) == null)
     {
          Mapper.CreateMap(typeof(TSource), typeof(TDestination));
     }
     return Mapper.Map<TSource, TDestination>(source);
}

That way, I don’t have to trap an exception with Mapper.DynamicMap.

Polerecky doesn’t like it, but I still do.