Drawing erase helper

By | Blog

For one of our app we needed drawing technique that would delete the image in graphics context based on the user input and act like a erase rubber.

Of course there is again everything needed in MonoTouch to do the job.


First we need to get the drawing context of an view object (UIImageView, but can be anything else) and prepare it for the painting. As the CGContext is LL (lower left) coordinates and not UL (upper left) coordinate system, there is a little trick needed to translate the coordinates.

 

public void EraseStart ()
{
	if (drawingBoard == null)
		return;
 
	UIGraphics.BeginImageContext (drawingBoard.Size);
 
	// erase lines
	ctx = UIGraphics.GetCurrentContext ();
 
	// Convert co-ordinate system to Cocoa's (origin in UL, not LL)
	ctx.TranslateCTM (0, drawingBoard.Size.Height);
	ctx.ConcatCTM (CGAffineTransform.MakeScale (1, -1));
 
	ctx.DrawImage (new RectangleF (0, 0, drawingBoard.Size.Width, drawingBoard.Size.Height),
             drawingBoard.CGImage);
}

 

Okay, that was not that bad, so now to the even easier part, to erase the content from the context and put it back to the view so it disappears from the view. To do that we will simply put clip to the context based on the point and we will add it as arc object.

 

public void ContextErasePoint (PointF point, int thickness)
{
	if (drawingBoard == null)
		return;
 
	RectangleF circleRect = new RectangleF (point.X - thickness / 2, 
		drawingBoard.Size.Height - point.Y - thickness / 2, thickness, thickness);
 
	ctx.AddArc (point.X, drawingBoard.Size.Height - point.Y, thickness / 2, 0f, 2f * (float)Math.PI, false);
	ctx.Clip();
 
	ctx.ClearRect (circleRect);
}

 

And now just put the image back from context to the image view so user can see it.

 

public void EraseEnd ()
{
	drawingBoard = UIGraphics.GetImageFromCurrentImageContext ();
	UIGraphics.EndImageContext ();
 
	drawingView.Image = drawingBoard;
}
 

 

That’s it.

Happy coding!

 


 

P.S. There is a sample project to download, with all the goodies working.
P.P.S. If you like my posts, please share the love and tell your friends and support me and buy my apps. Check touch4apps.com for more info. Thank you.

And here is the Github link to the sample code.
And sample video of it in action  

 

 

MonoTouch UITableViewController transparent background

By | Blog

Just came across a problem to have UITableViewController on a custom texture, wanted and needed to have the nice rounded corners as well and found out it is not that straightforward to set the background color to transparent in case that running on iPad (3.2 and 4.2 as well).

So here is a little code snippet that does the trick.

 

// create the UITableViewController
SettingTableViewController tableController = new SettingTableViewController(UITableViewStyle.Grouped);
 
// set its color to transparent as well, as this is needed, this is the first layer of the setup
tableController.View.BackgroundColor = UIColor.Clear;
tableController.View.Frame = new RectangleF(12, 101, 280, 98);
 
// create a dummy view for the underlaying view			
UIView view = new UIView();
view.Frame = tableController.View.Frame;
 
// make its color transparent as well
view.BackgroundColor = UIColor.Clear;
 
// and now the little trick, get to the core of the view hierarchy and set the view from the original UIImageView, in which the UITableView of the controller lays, to our custom transparent view
((UITableView)tableController.View).BackgroundView = view;
 
 

 

Happy coding!

 

MonoTouch infinite loop image scroll view

By | Blog

One of the clients wanted in the app a little feature for a menu system, something like a karusel, where specific menu items will be scrolled from side to side with a touch in an infinite loop.

I found out that it can be possible to do with a simple tweak to UIScrollView, handing the

scrollViewDidEndDecelerating from the UIScrollViewDelegate.

 

Guys from Monotouch team made the great job on this (as usual) and we have the delegate already available via built-in events, in this case DecelerationEnded.

So lets have a look at the implementation of the UIViewController class of some view, note we are adding the UI from the code, not from the nib file, just for simplicity. View has UIScrollView item and loads some images, last image is placed as the first one, then all images in the order and then first image as the last one.

Then the event for DecelerationEnded is handled to actually swap the position (fast – no animation) so user does not find out. For added more touch, the paging is enabled and of course the scroller is hidden, so it is not visible to the user where in the scrolling position he actually is.


 

public partial class ImageScrollViewController : UIViewController
	{
		#region Constructors
 
		// The IntPtr and initWithCoder constructors are required for items that need 
		// to be able to be created from a xib rather than from managed code
 
		public ImageScrollViewController (IntPtr handle) : base(handle)
		{
			Initialize p;lt;;span style="color: #000000;">();
		}
 
		[Export("initWithCoder:")]
		public ImageScrollViewController (NSCoder coder) : base(coder)
		{
			Initialize ();
		}
 
		public ImageScrollViewController () : base("ImageScrollViewController", null)
		{
			Initialize ();
		}
 
		void Initialize ()
		{
		}
 
		#endregion
 
		UIScrollView scrollView;
 
		public override void ViewDidLoad ()
		{
			base.ViewDidLoad ();
 
			scrollView = new UIScrollView (new RectangleF (0, 0, 320, 480));
			this.View.AddSubview (scrollView);
 
			// add the last image (image4) into the first position
			this.AddImageWithName ("Images/image4.jpg", 0);
 
			// add all of the images to the scroll view
			for (int i = 1; i < 5; i++) {
				this.AddImageWithName (string.Format ("Images/image{0}.jpg", i), i);
			}
 
			// add the first image (image1) into the last position
			this.AddImageWithName ("Images/image1.jpg", 5);
 
			scrollView.PagingEnabled = true;
			scrollView.Bounces = true;
			scrollView.DelaysContentTouches = true;
			scrollView.ShowsHorizontalScrollIndicator = false;
 
			scrollView.ContentSize = new System.Drawing.SizeF (1920, 480);
			scrollView.ScrollRectToVisible (new RectangleF (320, 0, 320, 480), true);
			scrollView.DecelerationEnded += HandleDecelerationEnded;
 
		}
 
		void HandleDecelerationEnded (object sender, EventArgs e)
		{
			if (scrollView.ContentOffset.X == 0) 
			{         
				scrollView.ScrollRectToVisible(new RectangleF(1280, 0, 320, 480), false);
			}    
			else if (scrollView.ContentOffset.X == 1600) 
			{         
				scrollView.ScrollRectToVisible(new RectangleF(320, 0, 320, 480), false);
			} 	
		}
 
		void AddImageWithName (string imageString, int position)
		{
			// add image to scroll view
			UIImage image = UIImage.FromFile (imageString);
			UIImageView imageView = new UIImageView (image);
 
			imageView.Frame = new System.Drawing.RectangleF (position * 320, 0, 320, 480);
 
			scrollView.AddSubview (imageView);
		}
	}

That is actually all there is to it. And github link: http://github.com/sichy/ImageScrollView

Happy coding!

MonoTouch set status bar hidden

By | Blog

If you ever needed to set the status bar hidden in Monotouch App, and wanted to do it properly for code that runs backward compatible on OS 3.X while developing on iOS SDK 4.X and also for newer devices, here is the answer:

 

if (UIApplication.SharedApplication.RespondsToSelector (new Selector ("setStatusBarHidden: withAnimation:")))
	UIApplication.SharedApplication.SetStatusBarHidden (true, UIStatusBarAnimation.Fade);
else
	UIApplication.SharedApplication.SetStatusBarHidden (true, true);
 

 

 

 

MonoTouch TapGesture without gesture recognizer

By | Blog

Few weeks ago I wrote a post about how to handle tap gesture on iOS prior gesture recognizers. In Objective-C there is simple trick with [self.nextResponder …] and then [NSObject cancelPreviousPerformRequestWithTarget …].

In MonoTouch NextResponder on Controller object is read only for some reason, and therefore cannot be used to implement this technique in a simple way. So a bit more coding is needed.


See the custom TapGestureRecognizer below:

public class DateTimeList : List<DateTime>
	{
		public UITouch Touch { get; set; }
 
		public DateTimeList (UITouch touch)
		{
			Touch = touch;
		}
	}
 
	public class TapGestureRecognizer
	{
		Dictionary <string, DateTimeList> touchEndedTimeMap = new Dictionary<string, DateTimeList>();
 
		public delegate void TouchEndedHandler (UITouch touch, int touchCount);
		public event TouchEndedHandler TouchEnded;
 
		public UIView View { get; set; }
 
		/// 
		/// 
		/// 
		public TapGestureRecognizer (UIView view)
		{
			View = view;
		}
 
		/// 
		/// 
		/// 
		public void CheckTouchEnded ()
		{
			{
				List<string> ptrToRemove = new List<string> ();
 
				foreach (string pointLocation in touchEndedTimeMap.Keys)
				{
					DateTimeList timeList = touchEndedTimeMap[pointLocation] as DateTimeList;
 
					// foreach time there check the current time agains the one there, if there are more 
					// than one in it, close enought to each other
					// i.e. less than 190ms and current time after the last one is more than 190ms as well 
					// then throw the tap count event for touchEnded
 
					// in fact it would be fine to check the last time in the list and fire the event with the 
					// amount of times in the list so we have the tapCount
					DateTime lastTime = timeList.Last ();
 
					TimeSpan span = new TimeSpan (DateTime.Now.Ticks - lastTime.Ticks);
 
					if (span.Milliseconds > 190)
					{
						ptrToRemove.Add (pointLocation);
 
						// fire the event, enter the amount of entries in the list
						if (TouchEnded != null)
							TouchEnded (timeList.Touch, timeList.Count);
					}
				}
 
				lock (touchEndedTimeMap) 
				{
					foreach (string point in ptrToRemove)
						touchEndedTimeMap.Remove (point);
 
					ptrToRemove.Clear ();
				}
			}
		}
 
		/// 
		/// 
		/// 
		///  
		/// A 
		/// 
		public void AddTouchesEnded (NSSet touches)
		{
			lock (touchEndedTimeMap)
			{
				foreach (UITouch touch in touches.ToArray<UITouch> ())
				{
					PointF location = touch.LocationInView (View);
 
					DateTimeList timeList;
 
					if (touchEndedTimeMap.ContainsKey(location.ToString()))
						timeList = touchEndedTimeMap[location.ToString()] as DateTimeList;
					else
					{
						timeList = new DateTimeList (touch);
 
						touchEndedTimeMap.Add (location.ToString(), timeList);
					}
 
					timeList.Add (DateTime.Now);
 
					Helper.PerformSelector (new DelegateFnc (CheckTouchEnded), 0, 200);
				}
			}
		}
	}

The code itself uses the Helper class from previous post for the PerformSelector with delay feature. The idea behind this is to actually delegate the touch events to the helper gesture recognizer class and just handle the event handler for TouchEnded event, which will return just the single and prepared UITouch object with the tap count info.

To use the class we need to instantiate it, for example in the viewDidLoad method:

TapGestureRecognizer tapGestureRecognizer = new TapGestureRecognizer(this.View);
tapGestureRecognizer.TouchEnded += HandleTapGestureRecognizerTouchEnded;

To handle the gestures:

void HandleTapGestureRecognizerTouchEnded (UITouch touch, int touchCount)
		{
			if (touchCount == 1)
			{
				// single tap
			}
			else if (touchCount == 2)
			{
				// double tap
			}
		}

 

And to be complete, we need to delegate the actual events to the recognizer, so in the touchesEnded handler call it:

public override void TouchesEnded (NSSet touches, UIEvent evt)
		{
			base.TouchesEnded (touches, evt);
 
			tapGestureRecognizer.AddTouchesEnded (touches);
		}

 

And thats all, works like a charm.

Happy coding!

 

MonoTouch PerformSelector Helper

By | Blog

It is a common issue in MonoTouch that function performSelector with delay attribute is not working out of the box, you need to fiddle with the NSRunLoop as per the comment from Geoff Norton from Novell:

PerformSelector with delay works fine, given that you understand its
restrictions.  It only works on threads with a NSRunLoop, and is completed
async.

So what to do, and do not want to or cannot get into the NSRunLoop “issue”?


MonoTouch with full C# again excels there with the nice ability to cheat a bit with NSTimer and .NET delegates.

 

Here is a short code snippet of the helper class:

public delegate void DelegateFnc ();
public delegate void DelegateViewFnc (UIView view);
 
public class HelperSelector
{
	public static void PerformSelector (DelegateFnc delegateFnc, int delaySeconds, int delayMiliSeconds)
	{
		NSTimer.CreateScheduledTimer (new TimeSpan (0, 0, 0, delaySeconds, delayMiliSeconds),
	               delegate { delegateFnc (); });
	}
 
	public static void PerformSelector (DelegateViewFnc delegateFnc, UIView view, int delaySeconds, int delayMiliSeconds)
	{
		NSTimer.CreateScheduledTimer (new TimeSpan (0, 0, 0, delaySeconds, delayMiliSeconds), 
			delegate { delegateFnc (view); });
	}
 
}

So there you go, simply call the HelperSelector.PerformSelector function to call it with a delay, I have prepared two delegates, one for void function with no params and void function with UIView param, as those are most used by me, anyone can add any other delegates and create an overloaded function in the helper class.

Now lets have a look how to call the function:

HelperSelector.PerformSelector (new DelegateViewFnc (StartHideAnimationForView), view, 5, 0);
 
public void StartHideAnimationForView (UIView view)
{
	UIView.BeginAnimations (null);
	UIView.SetAnimationDuration (0.5);
	UIView.SetAnimationDelegate (this);
 
	view.Alpha = 0;
 
	UIView.CommitAnimations ();
}

I call the StartHideAnimationForView with a delay and provided a simple code inside just for the sample purposes.

Happy coding!

iOS SDK cannot install

By | Blog

Ever had the problem installing any mpkg installer? Happened to me today, I received for no apparent reason information that JavaScript check failed during the iOS SDK installation. Was not able to resolve in a normal way, so had to hack a bit, here it is how (BTW works for any mpkg file, not only Xcode with iOS SDK).

1. extract the mpkg package with XAR (xar -xf path_to_your_installer.mpkg)
2. locate the dist file in the content subdirectory (iPhoneSDKSL.dist in this case)
3. open it in some text editor (vi will be fine in this case)
4. check the volume check function there and just change to JS code to return true:

function volumeCheck(){return true;}
function volumeCheck() { return true; }

5. close the text edit and save the file
6. repackage the mpkg back with XAR (xar -cf iOSDSDKInstall.mpkg path_to_the_extracted_location)
7. run the new package and all is well

BTW. if you use PathFinder app you can save lots of manual work, just right click the MPKG, select show content, then click the dist file and select open with and edit it in place. save it and that is it, PathFinder will repackage for you.

Happy hacking (in this case).

Show Modal UIViewController just after another one

By | Blog

You might come to a problem when you need to show new modal view controller just after another has been dismissed. And you want to preserve the animation even on dismiss.

Unfortunately it is not possible to call:

[self dismissModalViewControllerAnimated:TRUE];
 
[self presentModalViewController:picker animated:YES];
 

 

 As the second one will never show up.

@performSelector is the cure for our problem, just delay the call to the other modal view controller a bit:

[self dismissModalViewControllerAnimated:TRUE];
 
// schedule the open mail composer in a little delay
[self performSelector:@selector(sendMail) withObject:nil afterDelay: 0.45];

 

 Where in the sendMail function we call the obvious presentModalViewController on the mail picker controller.

Happy coding!

 

 

Affine Transform and UIView’s Frame vs. Center property

By | Blog

When using affine transform to rotate views (UIIMageView in most cases, right?) for example like this:

CGAffineTransform transform = 
	CGAffineTransformMakeRotation(-b->GetAngle());
 
oneView.transform = transform;

 

 Make sure you then translate the object using its center property and not using the frame property, as frame is already affected by the affine transform and your image would be corrupted.

Happy coding!

 

Add UILabel to UIToolbar

By | Blog

Had the need to show the datetime in the toolbar next to the touch button. Just adding the UILabel as subview to the UIToolbar makes the app to crash. But there is a solution:

 

1
2
3
4
5
6
7
8
9
UILabel* timeLabel = [[UILabel alloc] initWithFrame:CGRectMake(5, 5, 240, 20)];
timeLabel.backgroundColor = [UIColor clearColor];
timeLabel.textColor = [UIColor whiteColor];
 
// add your text here, using NSDateFormatter for example
timeLabel.text = @""; 
 
// create the bar button item where we specify the label as a view to use to init with     
UIBarButtonItem *dateTimeText = [[UIBarButtonItem alloc] initWithCustomView:timeLabel];

 

Happy coding!