Draw over a background image

AleStrAleStr ITMember

I’m developing a tool to draw freehand over a background image.
To start I developed a custom control that in the iOS implementation derives from UIView and I have override his Draw(CGRect rect) method to draw lines & co over it:

public override void Draw(CGRect rect)
{
foreach (var line in _DrawedObjs)
{
line.Color.SetStroke();
line.Path.Stroke();
}
}

In that case all works as expected and the lines are drawn over a white background.

To load the background image I had simply update my class LgcDrawView_iOS to derive from UIImageView instead of UIView as below:

public class LgcDrawView_iOS : UIImageView

but now my overridden Draw is not called any more.

Is that the right approach, or I must use derives from UIView and try to draw the image in the Draw(CGRect rect) method, like below

public override void Draw(CGRect rect)
{
// Draw Background image
DrawBitmap(_backgroundBitmap); ???

    // Draw custom objects
    foreach (var line in _DrawedObjs)
    {
        line.Color.SetStroke();
        line.Path.Stroke();
    }

}

That is the approach I use and works in Android View implementation

    canvas.DrawBitmap(_backgroundBitmap, 0, 0, _canvasPaint);

, but I don't know how to do the same in iOS UIVIew.

Any other idea?

Thanks

Tagged:

Best Answers

  • adamkempadamkemp mod US mod
    Accepted Answer

    Rather than disable the scrolling it makes more sense to enable/disable the gesture. The innermost gesture wins so if your gesture is on the inner view then it should override the scrolling. Disable the gesture to allow scrolling again.

Answers

  • AleStrAleStr ITMember

    As you told in another post UIImageView.Draw() can't be overridden.
    So I'm deriving from UIView, overriding his UIView.Draw() and drawing both the background image and after my custom draw objects as you suggest

        public override void Draw(CGRect rect)
        {
            if (_draw_bitmap && _image != null && !_draw_bitmap_done)
            {
                _image.Draw(rect);
            }
    
            foreach (var line in _DrawedObjs)
            {
                line.Color.SetStroke();
                line.Path.Stroke();
            }
        }
    

    but, has as you advance, now the drawing has performance problems (that not happens on Android).

    Instead, keep the UIImageView as a separate view and put your view on top of it.

    That is a good idea. Is enough that the my UIView has a transparent background?
    However the problem I see is how to "synchronize" the common events, like zoom and pan.
    Any good idea?

  • AleStrAleStr ITMember

    Mhh, I think I can use the following approach

    class MyCustomViewContainer : UIView
    {
    private UIImageView _backgroudImageView = null; // background
    private MyCustomViewDraw _drawView = null; // implementation of custom draw: lines, text, etc

    public override void Draw(CGRect rect)
        {
        _drawView.Draw(rect);
    }
    

    }

    where

    class MyCustomViewDraw : UIView
    {
    public override void Draw(CGRect rect)
    {
    // implementation of custom draw: lines, text, etc
    }
    }

    The gesture events are all captured by MyCustomViewContainer that dispatch they to _backgroudImageView or _drawView or both.

  • adamkempadamkemp mod USInsider, Developer Group Leader mod

    Zoom and pan are best handled by a UIScrollView, which can be a parent view of both. The hierarchy I would recommend is this:

    UIScrollView
        Container view
          Background image
          Custom drawing
    

    The container view is useful for zooming because UIScrollView natively supports zooming, and you just need to give it a single view to zoom.

    Of course your next problem is going to be gesture conflicts between panning and drawing, but that's a whole new topic.

  • AleStrAleStr ITMember

    I'm going crazy.

    I redesign the code as you suggest

    public class LgcDrawView_iOS : UIView
    {
        private LgcDrawView_iOS_Image _backgroundImageView = null;
        private LgcDrawView_iOS_Draw _drawView = null;
    
    ...
    
        public LgcDrawView_iOS(RectangleF frame, string imageSource)
            : base(frame)
        {
            _imageSource = imageSource;
    
            _backgroundImageView = new LgcDrawView_iOS_Image(new CGRect(0, 0, 757, 557));
            loadBackgroundBitmap();
            this.AddSubview(_backgroundImageView);
    
            _drawView = new LgcDrawView_iOS_Draw(frame);
            _drawView.BackgroundColor = UIColor.Clear;
            this.AddSubview(_drawView);
     }
    

    If I don't add the _backgroundImageView (//this.AddSubview(_backgroundImageView); commended) I can see the my custom draws, without the background of course.
    If I add the _backgroundImageView I can see the background, but not my custom draws. I verified that _drawView.Draw(CGRect rect) is executed, it Stroke() the draw objs, but nothing appears over the background image

  • AleStrAleStr ITMember

    I see your last post only after I posted my last one. I will do it, but I think that will not solve the last problem I posted.

    thanks anyway for your time

  • adamkempadamkemp mod USInsider, Developer Group Leader mod

    Your problem is probably related to some of the advice in this post. I don't know why the other view would have anything to do with that, though.

  • AleStrAleStr ITMember

    Got it!

    The project from where I started (http://www.infinite-x.net/2014/06/05/creating-a-drawing-app-using-xamarin-forms/) creates the Container view passing RectangleF.Empty as frame. So my Custom drawing use it also.

    Updating my code

    public LgcDrawView_iOS(RectangleF frame, string imageSource)
    : base(frame)
    {
    _imageSource = imageSource;

        _backgroundImageView = new LgcDrawView_iOS_Image(new CGRect(0, 0, 757, 557));
        loadBackgroundBitmap();
        this.AddSubview(_backgroundImageView);
    
        _drawView = new LgcDrawView_iOS_Draw(frame);  --> _drawView = new LgcDrawView_iOS_Draw(new RectangleF(0, 0, 757, 557)); 
        _drawView.BackgroundColor = UIColor.Clear;
        this.AddSubview(_drawView);
    

    Now it works. I can see the background and my custom drawings over it.

    Your article about frames raised me the right suspect!

  • AleStrAleStr ITMember

    I don't know how the original code may works :-(

  • AleStrAleStr ITMember

    Now I have hard wired the frame as new CGRect(0, 0, 757, 557) taking they from the CGRect instance passed to Draw(CGRect rect), but I have no idea how to calculate it

  • AleStrAleStr ITMember

    I tried also to follow your suggestion:

    Zoom and pan are best handled by a UIScrollView, which can be a parent view of both. The hierarchy I would recommend is this:

    UIScrollView
    Container view
    Background image
    Custom drawing

    updating my class as follow:

    public class LgcDrawView_iOS : UIScrollView <--------
    {
    public LgcDrawView_iOS(RectangleF frame, string imageSource)
    : base(frame)
    {
    _imageSource = imageSource;

            UIView container_View = new UIView(frame);      <--------
            this.AddSubview(container_View);                <--------
    
            _backgroundImageView = new LgcDrawView_iOS_Image(new CGRect(0, 0, 757, 557));
            loadBackgroundBitmap();
            container_View.AddSubview(_backgroundImageView);        <--------
    
            _drawView = new LgcDrawView_iOS_Draw(new RectangleF(0, 0, 757, 557));
            _drawView.BackgroundColor = UIColor.Clear;
            container_View.AddSubview(_drawView);                   <--------
    

    but seems have no effect. Zoom and pan are not working at all.

  • adamkempadamkemp mod USInsider, Developer Group Leader mod

    You need to read the documentation for UIScrollView. There's more to it than just putting the views inside.

  • AleStrAleStr ITMember

    Correct. I tried on the fly!

  • AleStrAleStr ITMember

    Of course your next problem is going to be gesture conflicts between panning and drawing, but that's a whole new topic.

    I know the problem. My app has a status that can be (A) ZoomPanning or (B) Drawing by user selection.
    In case A the Pan gesture translate the canvas (Android) or offset the frame (iOS), so it do pan.
    In case B add a new line from previous touched point to current touch point, so it draw.
    If I add a UIScrollView as parent view can I disable it in case B?
    I know I should search it on documentation, bur may be that is an easy question for you, and if the response is "No, you can't" I will search for another solution.

  • adamkempadamkemp mod USInsider, Developer Group Leader mod
    Accepted Answer

    Rather than disable the scrolling it makes more sense to enable/disable the gesture. The innermost gesture wins so if your gesture is on the inner view then it should override the scrolling. Disable the gesture to allow scrolling again.

  • AleStrAleStr ITMember

    Without Zoom it works perfectly.
    In Draw mode the DrawView UIPanGestureRecognizer wins and draw happens.
    In Zoom mode I remove the DrawView UIPanGestureRecognizer and pan happens.

    Enabling the UIScrollView Zoom it works, more or less, but...
    Until a certain zoom level the DrawView UIPanGestureRecognizer still wins.
    But if I zoom a little bit more the system seems confused and sometimes wins the DrawView UIPanGestureRecognizer, some time the UIScrollView pan.
    More the zoom is high more the UIScrollView pan wins.

    Do you have experience if if it is possible to set the GestureRecognizer sensibility to make the DrawView UIPanGestureRecognizer wins with any zoom factor?
    Or any other solution?

  • AleStrAleStr ITMember

    Got it:

    UIScrollView.PanGestureRecognizer.RequireGestureRecognizerToFail(DrawView.PanGestureRecognizer);

Sign In or Register to comment.