Skip to content

Instantly share code, notes, and snippets.

@praeclarum
Created June 24, 2011 00:37
Show Gist options
  • Save praeclarum/1043986 to your computer and use it in GitHub Desktop.
Save praeclarum/1043986 to your computer and use it in GitHub Desktop.
A UIScrollView that draws sharp dynamic graphics at any zoom scale
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using MonoTouch.CoreAnimation;
using MonoTouch.CoreGraphics;
using MonoTouch.UIKit;
namespace ScrollViewExperiments
{
/// <summary>
/// Contains a reference to the <see cref="CGContext"/> that
/// should be drawn to and a rectangle defining the visible
/// area on the screen.
/// </summary>
public class DrawingScrollViewDrawTileEventArgs : EventArgs {
/// <summary>
/// Draw to this
/// </summary>
public CGContext Context { get; set; }
/// <summary>
/// This is the area you should draw. Everything else is invisible
/// </summary>
public RectangleF VisibleRect { get; set; }
}
/// <summary>
/// Performs scrolling, zooming, and rendering of dynamic scenes.
/// Set its <see cref="ContentSize"/> to the size of your drawing.
/// Handle the <see cref="DrawTile"/> event to do the actual drawing.
/// </summary>
[MonoTouch.Foundation.Register("DrawingScrollView")]
public class DrawingScrollView : UIScrollView
{
UIView drawingContainerView = null;
int resolution = 0;
public UIView DrawingView { get; private set; }
public event EventHandler<DrawingScrollViewDrawTileEventArgs> DrawTile;
public int MinimumResolution { get; set; }
public int MaximumResolution { get; set; }
public DrawingScrollView (RectangleF frame)
: base (frame)
{
//
// The tileContainerView holds all the tiles
//
drawingContainerView = new UIView (RectangleF.Empty);
AddSubview (drawingContainerView);
//
// The DrawingView is what we use to save memory --
// don't have to render all of drawingContainerView
//
DrawingView = new TheDrawingView ();
drawingContainerView.AddSubview (DrawingView);
//
// Automatically handle the scrolling
//
Delegate = new Del ();
}
class Del : UIScrollViewDelegate
{
public override UIView ViewForZoomingInScrollView (UIScrollView scrollView)
{
return ((DrawingScrollView)scrollView).drawingContainerView;
}
public override void ZoomingEnded (UIScrollView scrollView, UIView view, float scale)
{
var tsv = (DrawingScrollView)scrollView;
//
// Work around iOS < 3.0 bug
//
tsv.SetZoomScaleBase (scale + 0.01f, false);
tsv.SetZoomScaleBase (scale, false);
//
// Update the display to
//
tsv.UpdateResolution ();
}
}
/// <summary>
/// This is the zoom scale of your drawing as displayed on the screen.
/// Don't trust the normal <see cref="ZoomScale"/> because we're hacking it.
/// </summary>
public virtual float DrawingZoomScale {
get {
return (float)(ZoomScale * Math.Pow (2, resolution));
}
}
/// <summary>
/// The visible bounds of the drawing.
/// Don't trust the normal <see cref="Bounds"/> because we're hacking it.
/// </summary>
public virtual RectangleF DrawingBounds {
get {
var scale = DrawingZoomScale;
var scrollBounds = Bounds;
var viewBounds = new RectangleF (
scrollBounds.X / scale, scrollBounds.Y / scale,
scrollBounds.Width / scale, scrollBounds.Height / scale);
return viewBounds;
}
}
public override void SetZoomScale (float scale, bool animated)
{
base.SetZoomScale (scale, animated);
if (!animated) {
UpdateResolution ();
}
}
void SetZoomScaleBase (float scale, bool animated)
{
base.SetZoomScale (scale, animated);
}
public void SetDrawingSize (SizeF size)
{
//
// Reset
//
ZoomScale = 1.0f;
MinimumZoomScale = 1.0f;
MaximumZoomScale = 1.0f;
resolution = 0;
//
// Configure
//
ContentSize = size;
drawingContainerView.Frame = new RectangleF (PointF.Empty, size);
//
// Force reload at new resolution
//
ReloadData ();
}
public override void LayoutSubviews ()
{
base.LayoutSubviews ();
//
// Position the tile
//
var scrollBounds = Bounds;
var tileFrame = ConvertRectToView (scrollBounds, drawingContainerView);
tileFrame.Intersect (drawingContainerView.Bounds);
DrawingView.Frame = tileFrame;
DrawingView.SetNeedsDisplay ();
}
protected virtual void OnDrawTile (object sender, DrawingScrollViewDrawTileEventArgs e)
{
var d = DrawTile;
if (d != null) {
try {
d (this, e);
}
catch (Exception err) {
Console.WriteLine (err);
}
}
}
void ReloadData ()
{
SetNeedsLayout ();
}
class TheDrawingView : UIView
{
public override void Draw (RectangleF rect)
{
var view = this;
var c = UIGraphics.GetCurrentContext ();
var super = view.Superview;
if (super == null) return;
var ssv = super.Superview as DrawingScrollView;
if (ssv == null) return;
var rvb = ssv.DrawingBounds;
if (rvb.Width == 0) return;
c.SaveState ();
var s = view.Frame.Width / rvb.Width;
c.ScaleCTM (s, s);
c.TranslateCTM (-rvb.X, -rvb.Y);
ssv.OnDrawTile (this, new DrawingScrollViewDrawTileEventArgs () {
Context = c,
VisibleRect = rvb,
});
c.RestoreState ();
}
}
void UpdateResolution ()
{
int delta = 0;
//
// Should we decrease the resolution?
//
for (var thisRes = MinimumResolution; thisRes < resolution; thisRes++) {
var thisDelta = thisRes - resolution;
var scaleCutoff = Math.Pow (2, thisDelta);
if (ZoomScale <= scaleCutoff) {
delta = thisDelta;
break;
}
}
//
// No? Should we increase it?
//
if (delta == 0) {
for (var thisRes = MaximumResolution; thisRes > resolution; thisRes--) {
var thisDelta = thisRes - resolution;
var scaleCutoff = Math.Pow (2, thisDelta - 1);
if (ZoomScale > scaleCutoff) {
delta = thisDelta;
break;
}
}
}
//
// OK, let's change it
//
if (delta != 0) {
resolution += delta;
var zoomFactor = (float)Math.Pow (2, delta * -1);
//
// Save the scroll state so that we can restore it later
//
var contentOffset = ContentOffset;
var contentSize = ContentSize;
var containerSize = drawingContainerView.Frame.Size;
//
// Futz
//
MaximumZoomScale = MaximumZoomScale * zoomFactor;
MinimumZoomScale = MinimumZoomScale * zoomFactor;
base.ZoomScale = ZoomScale * zoomFactor;
//
// Restore
//
ContentOffset = contentOffset;
ContentSize = contentSize;
drawingContainerView.Frame = new RectangleF (PointF.Empty, containerSize);
//
// Force reload at new resolution
//
ReloadData ();
}
}
}
}
@vfumin
Copy link

vfumin commented Jan 18, 2019

Hello!
Have You example code with using this class?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment