Monday 3 May 2010

iPad 'infinite scrolling' with MonoTouch

Tonights exercise proves that sometimes it's worth taking time out to 'design' a chunk of code before writing it. Here's the "design"... can you see what it's trying to do?


Okay, I'll tell you: I'm trying to create an 'infinite' (well, just a configurable number) of scrollable pages that are rendered with UIWebView controls. I want to use the minimum number of controls for performance and memory but I also want a nice scrolling experience where the next and previous pages are aways pre-loaded. As you scroll left and right, the UIWebViews are shuffled around (X-coordinates only) to create an effect similar to the Time magazine 'app'.

Amazingly it worked out pretty quickly - here are a couple of screens to "prove" that it scrolls left & right as expected. It's using a "CSS3" multi-column hack in the Html, but that's another story...


And here are the 'important bits' of the code. I'm not publishing the entire source right now - the app still has a long way to go - but you get the idea... I hope. I only had a brief look around for existing examples so if you've seen a better one, let me know!

// declarations
UIScrollView MyScrollView;
UIWebView[] WebViewArray = new UIWebView[3];
int[] WebViewHasPage = new int[]{0,1,2};
int NumberOfPages = 8; // news01.html .. news08.html
...
// ViewController ctor
var bounds = new RectangleF(0, 0, View.Bounds.Width, View.Bounds.Height);
MyScrollView = new UIScrollView(bounds);
RectangleF scrollFrame = MyScrollView.Frame; // start with screen size
scrollFrame.Width = PageWidth * NumberOfPages; // then make the content
MyScrollView.ContentSize = scrollFrame.Size; // 8 screens wide
MyScrollView.Scrolled += ScrollViewScrolled; // and attach event handler
for (int i = 0; i < 3; i++)
{ // lay out three web controls to shuffle around
webViewFrame.X = PageWidth * i;
WebViewArray[i] = new UIWebView(webViewFrame);
WebViewArray[i].ScalesPageToFit = false;
var u = NSUrl.FromFilename(basedir+"news0"+(i+1)+".html");
var r = new NSUrlRequest(u);
WebViewArray[i].LoadRequest(r);
MyScrollView.AddSubview (WebViewArray[i]);
}
...
// and the method that does the shuffling
private void ScrollViewScrolled (object sender, EventArgs e)
{
UIScrollView sv = (UIScrollView)sender;
double page = Math.Floor ((sv.ContentOffset.X - sv.Frame.Width / 2) / sv.Frame.Width) + 1;
int LastPage = CurrentPage;
int NewPage = CurrentPage;
if (sv.ContentOffset.X % PageWidth == 0) NewPage = (int)page; // moved pages!
if (NewPage >= 0 && NewPage < NumberOfPages)
{
pageControl.CurrentPage = NewPage;
CurrentPage = NewPage;
int updatePage = 0;
bool shouldMove = false;
if (NewPage > LastPage)
{// moving right
updatePage = NewPage + 1; shouldMove = true;
}
else if (NewPage < LastPage)
{// moving left
updatePage = CurrentPage - 1; shouldMove = true;
}
if (shouldMove & (updatePage >= 0) & (updatePage < NumberOfPages))
{
int webViewSlot = updatePage % 3;
if (WebViewHasPage[webViewSlot] == updatePage) return; // already has it
else WebViewHasPage[webViewSlot] = updatePage;
var url = NSUrl.FromFilename(BaseDirectory+"news0"+(updatePage+1)+".html");
var request = new NSUrlRequest(url);
WebViewArray[webViewSlot].LoadRequest(request);
var rect = new RectangleF (
WebViewArray[0].Frame.Width * updatePage
, WebViewArray[webViewSlot].Frame.Y
, WebViewArray[webViewSlot].Frame.Width
, WebViewArray[webViewSlot].Frame.Height);
WebViewArray[webViewSlot].Frame = rect;
}
}
else
{
Console.WriteLine ("Scrolled a little too far, to page " + page);
}
}