Some years ago I made a class in Xamarin.iOS which was simply a UIView that snaps to the edges of the screen.
public class MovableUIButton : UIView
{
private UIPanGestureRecognizer PanGesture;
private UIButton btnShoppingCart;
private UIDynamicAnimator animator;
private PointF snapPoint;
private bool isInitialized = false;
private UISnapBehavior snap;
// offsets used to position image relative to touch point while being dragged
private float dx = 0;
private float dy = 0;
public event EventHandler TouchUpInside;
public MovableUIButton(CGRect rect) : base(rect)
{
Initialize();
}
public MovableUIButton(IntPtr handle) : base(handle)
{
Initialize();
}
private void Initialize()
{
PanGesture = new UIPanGestureRecognizer(DidPan);
this.AddGestureRecognizer(PanGesture);
// Make it round
this.Layer.CornerRadius = this.Bounds.Width / 2;
this.BackgroundColor = UIColor.FromRGB(89f / 255f, 157f / 255f, 255f / 255f);
// Set the Border
this.Layer.BorderColor = UIColor.White.CGColor;
this.Layer.BorderWidth = 3;
// Set a Shadow
this.Layer.ShadowColor = UIColor.White.CGColor;
this.Layer.ShadowOpacity = .5f;
this.Layer.ShadowRadius = 8.0f;
this.Layer.ShadowOffset = new System.Drawing.SizeF(0f, 0f);
btnShoppingCart = new UIButton(Bounds);
btnShoppingCart.Font = FontAwesome.Font(30);
btnShoppingCart.SetTitle(BaseFontAwesome.FAShoppingCart, UIControlState.Normal);
btnShoppingCart.UserInteractionEnabled = true;
btnShoppingCart.TouchUpInside += BtnShoppingCart_TouchUpInside;
this.AddSubview(btnShoppingCart);
}
private void InitializeAnimator()
{
snapPoint = new PointF((float)SetX(Superview.Bounds), (float)SetY(Superview.Bounds));
animator = new UIDynamicAnimator(Superview);
isInitialized = true;
}
private void DidPan()
{
if (isInitialized == false)
{
InitializeAnimator();
}
if ((PanGesture.State == UIGestureRecognizerState.Began || PanGesture.State == UIGestureRecognizerState.Changed) && (PanGesture.NumberOfTouches == 1))
{
// remove any previosuly applied snap behavior to avoid a flicker that will occur if both the gesture and physics are operating on the view simultaneously
if (snap != null)
animator.RemoveBehavior(snap);
var p0 = PanGesture.LocationInView(Superview);
if (dx == 0)
dx = (float)(p0.X - this.Center.X);
if (dy == 0)
dy = (float)(p0.Y - this.Center.Y);
// this is where the offsets are applied so that the location of the image follows the point where the image is touched as it is dragged,
// otherwise the center of the image would snap to the touch point at the start of the pan gesture
var p1 = new PointF((float)(p0.X - dx), (float)(p0.Y - dy));
this.Center = p1;
}
else if (PanGesture.State == UIGestureRecognizerState.Ended)
{
// reset offsets when dragging ends so that they will be recalculated for next touch and drag that occurs
dx = 0;
dy = 0;
snapPoint = new PointF((float)SetX(Superview.Bounds), (float)SetY(Superview.Bounds));
SnapImageIntoPlace((System.Drawing.PointF)PanGesture.LocationInView(Superview));
}
}
void SnapImageIntoPlace(PointF touchPoint)
{
snap = new UISnapBehavior(this, snapPoint);
animator.AddBehavior(snap);
}
private nfloat SetX(CGRect superBounds)
{
nfloat x = 0f;
if (this.Center.X > superBounds.Width / 2)
{
x = superBounds.Width - 20;
}
else
{
x = 20;
}
return x;
}
private nfloat SetY(CGRect superBounds)
{
nfloat y = 0f;
if (this.Center.Y < 20)
{
y = 20;
}
else if (this.Center.Y > superBounds.Height - 20)
{
y = superBounds.Height - 20;
}
else
{
return this.Center.Y;
}
return y;
}
public override void TouchesBegan(NSSet touches, UIEvent evt)
{
base.TouchesBegan(touches, evt);
}
private void BtnShoppingCart_TouchUpInside(object sender, EventArgs e)
{
TouchUpInside?.Invoke(this, null);
}
}
This class contains a UISnapBehavior object which is not available in Xamarin.Forms. Because I want to port this to Xamarin.Forms, is there an easy workaround or a similar class to UISnapBehavior?
Not sure if you're still waiting for an answer to this two years later, but hopefully this can still be useful to someone.
You should be able to take this class and put it in your .iOS project and create a custom renderer for a custom control in you pcl. something like this:
Custom Renderer in your iOS project:
Custom Control to be put into the pcl:
the XAML on whatever page you use it on: